Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(redm/script): Add fxmanifest metadata for overriding train track and trolley cable files #2430

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion code/client/clrcore-v2/BaseScript.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,6 @@ internal void RegisterKeyMap(string command, string description, string inputMap
#else
if (inputMapper != null && inputParameter != null)
{
Debug.WriteLine(command);
Native.CoreNatives.RegisterKeyMapping(command, description, inputMapper, inputParameter);
}
m_commands.Add(new KeyValuePair<int, DynFunc>(ReferenceFunctionManager.CreateCommand(command, dynFunc, false), dynFunc));
Expand Down
24 changes: 22 additions & 2 deletions code/client/clrcore-v2/Coroutine/Scheduler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public static void Schedule(Action coroutine)
lock (s_nextFrame)
{
s_nextFrame.Add(coroutine);
ScriptInterface.RequestTickNextFrame();
}
}
else
Expand Down Expand Up @@ -72,14 +73,30 @@ public static void Schedule(Action coroutine, TimePoint time)
lock (s_queue)
{
// linear ordered insert, performance improvement might be a binary tree (i.e.: priority queue)
for (var it = s_queue.First; it != null; it = it.Next)
var it = s_queue.First;
if (it != null)
{
// if added to the front, we'll also need to request for a tick/call-in
if (time < it.Value.Item1)
{
s_queue.AddBefore(it, new Tuple<ulong, Action>(time, coroutine));
s_queue.AddFirst(new Tuple<ulong, Action>(time, coroutine));
ScriptInterface.RequestTick(time);

return;
}

// check next
for (it = it.Next; it != null; it = it.Next)
{
if (time < it.Value.Item1)
{
s_queue.AddBefore(it, new Tuple<ulong, Action>(time, coroutine));
return;
}
}
}
else
ScriptInterface.RequestTick(time);

s_queue.AddLast(new Tuple<ulong, Action>(time, coroutine));
}
Expand Down Expand Up @@ -168,7 +185,10 @@ internal static void Update()
}
}
else
{
ScriptInterface.RequestTick(curIt.Value.Item1);
return;
}
}
}
}
Expand Down
19 changes: 19 additions & 0 deletions code/client/clrcore-v2/Interop/Types/ScriptSharedData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Runtime.InteropServices;
using System.Threading;

namespace CitizenFX.Core
{
[StructLayout(LayoutKind.Explicit)]
internal struct ScriptSharedData
{
/// <summary>
/// Next time when our host needs to call in again
/// </summary>
[FieldOffset(0)] public ulong m_scheduledTime;

/// <summary>
/// Same as <see cref="m_scheduledTime"/> but used in methods like <see cref="Interlocked.CompareExchange(ref long, long, long)" /> who miss a <see cref="ulong"/> overload.
/// </summary>
[FieldOffset(0)] public long m_scheduledTimeAsLong;
};
}
45 changes: 29 additions & 16 deletions code/client/clrcore-v2/ScriptInterface.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Threading;

#if IS_FXSERVER
using ContextType = CitizenFX.Core.fxScriptContext;
Expand All @@ -12,8 +12,6 @@

/*
* Notes while working on this environment:
* - Scheduling: any function that can potentially add tasks to the C#'s scheduler needs to return the time
* of when it needs to be activated again, which then needs to be scheduled in the core scheduler (bookmark).
*/

namespace CitizenFX.Core
Expand All @@ -25,6 +23,8 @@ internal static class ScriptInterface
internal static string ResourceName { get; private set; }
internal static CString CResourceName { get; private set; }

private static unsafe ScriptSharedData* s_sharedData;

#region Callable from C#

[SecurityCritical, MethodImpl(MethodImplOptions.InternalCall)]
Expand Down Expand Up @@ -71,16 +71,37 @@ internal static class ScriptInterface
[SecurityCritical]
internal static unsafe bool ReadAssembly(string file, out byte[] assembly, out byte[] symbols) => ReadAssembly(s_runtime, file, out assembly, out symbols);

/// <summary>
/// Schedule a call-in at the given time, uses CAS to make sure we only overwrite if the given time is earlier than the stored one.
/// </summary>
/// <param name="time">Next time to request a call in</param>
[SecuritySafeCritical]
internal static unsafe void RequestTick(ulong time)
{
ulong prevTime = (ulong)Interlocked.Read(ref s_sharedData->m_scheduledTimeAsLong);
while (time < prevTime)
{
prevTime = (ulong)Interlocked.CompareExchange(ref s_sharedData->m_scheduledTimeAsLong, (long)time, (long)prevTime);
}
}

/// <summary>
/// Schedule a call-in for the next frame.
/// </summary>
[SecuritySafeCritical]
public static unsafe void RequestTickNextFrame() => s_sharedData->m_scheduledTime = 0UL; // 64 bit read/writes – while aligned – are atomic on 64 bit machines

#endregion

#region Called by Native
[SecurityCritical, SuppressMessage("System.Diagnostics.CodeAnalysis", "IDE0051", Justification = "Called by host")]
internal static void Initialize(string resourceName, UIntPtr runtime, int instanceId)
private static unsafe void Initialize(string resourceName, UIntPtr runtime, int instanceId, ScriptSharedData* sharedData)
{
s_runtime = runtime;
InstanceId = instanceId;
ResourceName = resourceName;
CResourceName = resourceName;
s_sharedData = sharedData;

Resource.Current = new Resource(resourceName);
Debug.Initialize(resourceName);
Expand All @@ -93,7 +114,7 @@ internal static void Initialize(string resourceName, UIntPtr runtime, int instan
}

[SecurityCritical, SuppressMessage("System.Diagnostics.CodeAnalysis", "IDE0051", Justification = "Called by host")]
internal static ulong Tick(ulong hostTime, bool profiling)
internal static void Tick(ulong hostTime, bool profiling)
{
Scheduler.CurrentTime = (TimePoint)hostTime;
Profiler.IsProfiling = profiling;
Expand All @@ -107,12 +128,10 @@ internal static ulong Tick(ulong hostTime, bool profiling)
{
Debug.PrintError(e, "Tick()");
}

return Scheduler.NextTaskTime();
}

[SecurityCritical, SuppressMessage("System.Diagnostics.CodeAnalysis", "IDE0051", Justification = "Called by host")]
internal static unsafe ulong TriggerEvent(string eventName, byte* argsSerialized, int serializedSize, string sourceString, ulong hostTime, bool profiling)
internal static unsafe void TriggerEvent(string eventName, byte* argsSerialized, int serializedSize, string sourceString, ulong hostTime, bool profiling)
{
Scheduler.CurrentTime = (TimePoint)hostTime;
Profiler.IsProfiling = profiling;
Expand All @@ -136,30 +155,24 @@ internal static unsafe ulong TriggerEvent(string eventName, byte* argsSerialized
EventsManager.IncomingEvent(eventName, sourceString, origin, argsSerialized, serializedSize, args);
}
}

return Scheduler.NextTaskTime();
}

[SecurityCritical, SuppressMessage("System.Diagnostics.CodeAnalysis", "IDE0051", Justification = "Called by host")]
internal static unsafe ulong LoadAssembly(string name, ulong hostTime, bool profiling)
internal static unsafe void LoadAssembly(string name, ulong hostTime, bool profiling)
{
Scheduler.CurrentTime = (TimePoint)hostTime;
Profiler.IsProfiling = profiling;

ScriptManager.LoadAssembly(name, true);

return Scheduler.NextTaskTime();
}

[SecurityCritical, SuppressMessage("System.Diagnostics.CodeAnalysis", "IDE0051", Justification = "Called by host")]
internal static unsafe ulong CallRef(int refIndex, byte* argsSerialized, uint argsSize, out IntPtr retvalSerialized, out uint retvalSize, ulong hostTime, bool profiling)
internal static unsafe void CallRef(int refIndex, byte* argsSerialized, uint argsSize, out IntPtr retvalSerialized, out uint retvalSize, ulong hostTime, bool profiling)
{
Scheduler.CurrentTime = (TimePoint)hostTime;
Profiler.IsProfiling = profiling;

ReferenceFunctionManager.IncomingCall(refIndex, argsSerialized, argsSize, out retvalSerialized, out retvalSize);

return Scheduler.NextTaskTime();
}

[SecurityCritical, SuppressMessage("System.Diagnostics.CodeAnalysis", "IDE0051", Justification = "Called by host")]
Expand Down
17 changes: 17 additions & 0 deletions code/components/citizen-resources-gta/src/ResourcesTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ namespace streaming
void RemoveDataFileFromLoadList(const std::string& type, const std::string& path);

void SetNextLevelPath(const std::string& path);

#if defined(IS_RDR3)
void SetTrainTrackFilePath(const std::string& path);
void SetTrolleyCableFilePath(const std::string& path);
#endif
}
#endif

Expand Down Expand Up @@ -188,6 +193,18 @@ static InitFunction initFunction([] ()
streaming::SetNextLevelPath(resourceRoot + meta.second);
}

#if defined(IS_RDR3)
for (auto& meta : metaData->GetEntries("replace_traintrack_file"))
{
streaming::SetTrainTrackFilePath(resourceRoot + meta.second);
}

for (auto& meta : metaData->GetEntries("replace_trolley_cable_file"))
{
streaming::SetTrolleyCableFilePath(resourceRoot + meta.second);
}
#endif

if (!RangeLengthMatches(metaData->GetEntries("data_file"), metaData->GetEntries("data_file_extra")))
{
GlobalError("data_file entry count mismatch in resource %s", resource->GetName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
#include <om/OMComponent.h>

#include "MonoMethods.h"
#include "ScriptSharedData.h"

namespace fx::mono
{
class MonoScriptRuntime : public fx::OMClass<MonoScriptRuntime, IScriptRuntime, IScriptFileHandlingRuntime, IScriptTickRuntimeWithBookmarks,
class MonoScriptRuntime : public fx::OMClass<MonoScriptRuntime, IScriptRuntime, IScriptFileHandlingRuntime, IScriptTickRuntime,
IScriptEventRuntime, IScriptRefRuntime, IScriptMemInfoRuntime, /*IScriptStackWalkingRuntime,*/ IScriptDebugRuntime, IScriptProfiler>
{
private:
Expand All @@ -35,22 +36,22 @@ class MonoScriptRuntime : public fx::OMClass<MonoScriptRuntime, IScriptRuntime,
IScriptHost* m_scriptHost;
IScriptHostWithResourceData* m_resourceHost;
IScriptHostWithManifest* m_manifestHost;
IScriptHostWithBookmarks* m_bookmarkHost;

fx::OMPtr<IScriptRuntimeHandler> m_handler;
fx::Resource* m_parentObject;
IDebugEventListener* m_debugListener;
std::unordered_map<std::string, int> m_scriptIds;
uint64_t m_scheduledTime = ~uint64_t(0);

ScriptSharedData m_sharedData;

// method targets
Method m_loadAssembly;

// method thunks, these are for calls that require performance
Thunk<uint32_t(uint64_t gameTime, bool profiling)> m_tick;
Thunk<uint32_t(MonoString* eventName, const char* argsSerialized, uint32_t serializedSize, MonoString* sourceId, uint64_t gameTime, bool profiling)> m_triggerEvent;
Thunk<void(uint64_t gameTime, bool profiling)> m_tick;
Thunk<void(MonoString* eventName, const char* argsSerialized, uint32_t serializedSize, MonoString* sourceId, uint64_t gameTime, bool profiling)> m_triggerEvent;

Thunk<uint32_t(int32_t refIndex, char* argsSerialized, uint32_t argsSize, char** retvalSerialized, uint32_t* retvalSize, uint64_t gameTime, bool profiling)> m_callRef;
Thunk<void(int32_t refIndex, char* argsSerialized, uint32_t argsSize, char** retvalSerialized, uint32_t* retvalSize, uint64_t gameTime, bool profiling)> m_callRef;
Thunk<void(int32_t refIndex, int32_t* newRefIdx)> m_duplicateRef = nullptr;
Thunk<void(int32_t refIndex)> m_removeRef = nullptr;

Expand All @@ -75,9 +76,7 @@ class MonoScriptRuntime : public fx::OMClass<MonoScriptRuntime, IScriptRuntime,

NS_DECL_ISCRIPTFILEHANDLINGRUNTIME;

NS_DECL_ISCRIPTTICKRUNTIMEWITHBOOKMARKS;

void ScheduleTick(uint64_t timeMilliseconds);
NS_DECL_ISCRIPTTICKRUNTIME;

NS_DECL_ISCRIPTEVENTRUNTIME;

Expand All @@ -97,13 +96,4 @@ inline MonoScriptRuntime::MonoScriptRuntime()
m_instanceId = rand();
m_name = "ScriptDomain_" + std::to_string(m_instanceId);
}

inline void MonoScriptRuntime::ScheduleTick(uint64_t timeMilliseconds)
{
if (timeMilliseconds < m_scheduledTime)
{
m_scheduledTime = timeMilliseconds;
m_bookmarkHost->ScheduleBookmark(this, 0, timeMilliseconds * 1000); // positive values are expected to me microseconds and absolute
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once

#include <atomic>

namespace fx::mono
{
struct ScriptSharedData
{
public:
std::atomic<uint64_t> m_scheduledTime = ~uint64_t(0);
};
}
Loading
Loading