1212#include < File/File.hpp>
1313#include < LuaMadeSimple/LuaMadeSimple.hpp>
1414#include < Mod/Mod.hpp>
15+ #include < SettingsManager.hpp>
1516
1617#include < String/StringType.hpp>
1718
@@ -54,6 +55,35 @@ namespace RC
5455 int32_t lua_action_thread_ref{};
5556 };
5657
58+ // Status of a delayed action (mirrors UE's ETimerStatus)
59+ enum class DelayedActionStatus : uint8_t
60+ {
61+ Pending, // Created but not yet started
62+ Active, // Running, waiting for delay to expire
63+ Paused, // Timer paused, will resume when unpaused
64+ Executing, // Currently executing callback
65+ PendingRemoval // Marked for removal, will be cleaned up
66+ };
67+
68+ struct DelayedGameThreadAction
69+ {
70+ const LuaMadeSimple::Lua* lua;
71+ int32_t lua_action_function_ref{};
72+ int32_t lua_action_thread_ref{};
73+ GameThreadExecutionMethod method{GameThreadExecutionMethod::EngineTick};
74+ DelayedActionStatus status{DelayedActionStatus::Active};
75+ std::chrono::steady_clock::time_point execute_at{}; // Absolute time when action should execute
76+ int64_t time_remaining_ms{0 }; // Time remaining when paused (milliseconds)
77+ int64_t frames_remaining{0 }; // Countdown for frame-based delays
78+ int64_t delay_ms{0 }; // Original delay in milliseconds (for loop/reset)
79+ int64_t delay_frames{0 }; // Original delay in frames (0 means use time-based delay)
80+ int64_t handle{0 }; // Unique handle for this action
81+ bool is_retriggerable{false }; // If true, can be reset by calling with same handle
82+ bool is_looping{false }; // If true, re-schedule after each execution
83+ };
84+
85+ static inline int64_t m_next_delayed_action_handle{1 };
86+
5787 struct AsyncAction
5888 {
5989 // TODO: Use LuaMadeSimple instead of lua_State*
@@ -111,6 +141,9 @@ namespace RC
111141 static inline std::unordered_map<File::StringType, LuaCallbackData> m_global_command_lua_callbacks;
112142 static inline std::unordered_map<File::StringType, LuaCallbackData> m_custom_command_lua_pre_callbacks;
113143 static inline std::vector<SimpleLuaAction> m_game_thread_actions{};
144+ static inline std::vector<SimpleLuaAction> m_engine_tick_actions{};
145+ static inline std::vector<DelayedGameThreadAction> m_delayed_game_thread_actions{};
146+ static inline GameThreadExecutionMethod m_default_game_thread_method{GameThreadExecutionMethod::EngineTick};
114147 // This is storage that persists through hot-reloads.
115148 static inline std::unordered_map<std::string, SharedLuaVariable> m_shared_lua_variables{};
116149 static inline std::vector<FunctionHookData> m_custom_event_callbacks{};
@@ -131,9 +164,11 @@ namespace RC
131164
132165 private:
133166 std::jthread m_async_thread;
167+ std::thread::id m_main_thread_id{};
134168 bool m_processing_events{};
135169 bool m_pause_events_processing{};
136170 bool m_is_process_event_hooked{};
171+ static inline bool m_is_engine_tick_hooked{};
137172 std::mutex m_actions_lock{};
138173
139174 public:
@@ -147,6 +182,9 @@ namespace RC
147182 }
148183
149184 private:
185+ static auto ensure_engine_tick_hooked () -> void;
186+ static auto ensure_process_event_hooked (LuaMod* mod) -> void;
187+
150188 static auto custom_module_searcher (lua_State* L) -> int;
151189 auto setup_custom_module_loader (const LuaMadeSimple::Lua* lua_state) -> void;
152190 auto load_and_execute_script (const std::filesystem::path& script_path) -> bool;
@@ -179,6 +217,16 @@ namespace RC
179217 m_actions_lock.unlock ();
180218 }
181219
220+ [[nodiscard]] RC_UE4SS_API auto get_async_thread_id () const -> std::thread::id
221+ {
222+ return m_async_thread.get_id ();
223+ }
224+
225+ [[nodiscard]] RC_UE4SS_API auto get_main_thread_id () const -> std::thread::id
226+ {
227+ return m_main_thread_id;
228+ }
229+
182230 public:
183231 // Called once when the program is starting, after mods are setup but before any mods have been started
184232 auto static on_program_start () -> void;
0 commit comments