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

Changes for better script online compatibility #369

Merged
merged 20 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0c65be3
WIP rollback function hook
estebanfer Feb 2, 2024
5a590a5
add local_data to StateMemory, to allow script data that gets affecte…
estebanfer Feb 9, 2024
2543422
hook rollback function with patch_and_redirect and fix it
estebanfer Feb 11, 2024
9a994e4
Merge branch 'main' into online-compat-stuff
estebanfer Feb 12, 2024
bbffae5
Merge branch 'main' into online-compat-stuff
estebanfer Mar 6, 2024
a91419c
Move local_state_datas to LocalStateData struct
estebanfer Mar 6, 2024
f2e4f8b
Merge branch 'main' into online-compat-stuff
estebanfer Mar 7, 2024
e70fa55
make more callbacks online compatible, fix deadlock
estebanfer Mar 8, 2024
79c13f3
format
estebanfer Mar 8, 2024
43386cb
docs, rename clone_heap -> copy_state, state local_data -> user_data
estebanfer Mar 29, 2024
8df6529
change state location to read-only via getter
estebanfer Mar 31, 2024
488d643
trigger PRE_COPY_STATE on Overlunky savestates
estebanfer Apr 10, 2024
c704236
move State::get_location to header file
estebanfer Apr 10, 2024
3881f06
minor autodoc fix
estebanfer Apr 11, 2024
7e9c411
remove PRE_COPY_STATE lua event for now
estebanfer May 1, 2024
f105e88
fix get_local_* return type in docs
estebanfer May 3, 2024
f93ee54
fix spawn_api and custom_allocator online
estebanfer Jun 3, 2024
4b1a8b2
Merge branch 'main' into online-compat-stuff
estebanfer Aug 3, 2024
98cb792
better deepcopy_object implementation
estebanfer Aug 3, 2024
9f70235
change get_location to get_offset, remove get_state_offset
estebanfer Aug 3, 2024
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
4 changes: 2 additions & 2 deletions src/game_api/memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,10 @@ size_t patch_and_redirect(size_t addr, size_t replace_size, const std::string_vi
}
else
{
std::memcpy(new_code + data_size_to_move, payload.data(), payload.size());
std::memcpy(new_code, payload.data(), payload.size());

if (!just_nop)
std::memcpy(new_code, (void*)addr, data_size_to_move);
std::memcpy(new_code + payload.size(), (void*)addr, data_size_to_move);
}

size_t return_addr = addr + replace_size;
Expand Down
10 changes: 10 additions & 0 deletions src/game_api/script/events.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -517,3 +517,13 @@ void post_event(ON event)
return true;
});
}

void pre_heap_clone_event(StateMemory* from, StateMemory* to)
{
LuaBackend::for_each_backend(
[&](LuaBackend::LockedBackend backend)
{
backend->pre_clone_heap(from, to);
return true;
});
}
1 change: 1 addition & 0 deletions src/game_api/script/events.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ void post_init_layer(LAYER layer);
void post_unload_layer(LAYER layer);
void post_room_generation();
void post_level_generation();
void pre_heap_clone_event(StateMemory* from, StateMemory* to);
void post_save_state(int slot, StateMemory* saved);
void post_load_state(int slot, StateMemory* loaded);

Expand Down
125 changes: 86 additions & 39 deletions src/game_api/script/lua_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,27 +36,31 @@
#include "usertypes/vanilla_render_lua.hpp" // for VanillaRenderContext
#include "window_api.hpp" // for get_window

std::recursive_mutex g_all_backends_mutex;
std::vector<std::unique_ptr<LuaBackend::ProtectedBackend>> g_all_backends;
std::unordered_map<int, HotKey> g_hotkeys;
int g_hotkey_count = 0;

LuaBackend::LuaBackend(SoundManager* sound_mgr, LuaConsole* con)
: lua{get_lua_vm(sound_mgr), sol::create}, vm{acquire_lua_vm(sound_mgr)}, sound_manager{sound_mgr}, console{con}
{
g_state = State::get().ptr_main();
g_state = State::get().ptr_local();
if (g_state == nullptr)
{
g_state = State::get().ptr_main();
}
ScriptState& state = local_state_datas[g_state].state;
Comment on lines +47 to +52
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't backend constructed ASAP in playlunky? then this will always be main state anyway

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure about when they are constructed, are they when you create a new script in OL?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was thinking more about PL

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah but this code is for both PL and OL, also handling what happens in OL makes sense to me

state.screen = g_state->screen;
state.time_level = g_state->time_level;
state.time_total = g_state->time_total;
state.time_global = get_frame_count_main();
state.time_global = State::get_frame_count(g_state);
state.frame = state.frame;
state.loading = g_state->loading;
state.reset = (g_state->quest_flags & 1);
state.quest_flags = g_state->quest_flags;

populate_lua_env(lua);

std::lock_guard lock{g_all_backends_mutex};
std::lock_guard lock{global_lua_lock};
g_all_backends.emplace_back(new ProtectedBackend{this});
self = g_all_backends.back().get();
}
Expand All @@ -76,12 +80,17 @@ LuaBackend::~LuaBackend()
}

{
std::lock_guard lock{g_all_backends_mutex};
std::lock_guard lock{global_lua_lock};
std::erase_if(g_all_backends, [=](const std::unique_ptr<ProtectedBackend>& protected_backend)
{ return protected_backend.get() == self; });
}
}

LocalStateData& LuaBackend::get_locals()
{
return local_state_datas[State::get().ptr()];
}

void LuaBackend::clear()
{
clear_all_callbacks();
Expand Down Expand Up @@ -274,47 +283,49 @@ bool LuaBackend::update()
}

/*moved to pre_load_screen
if (g_state->loading == 1 && g_state->loading != state.loading && g_state->screen_next != (int)ON::OPTIONS && g_state->screen != (int)ON::OPTIONS && g_state->screen_last != (int)ON::OPTIONS)
if (state->loading == 1 && state->loading != script_state.loading && state->screen_next != (int)ON::OPTIONS && state->screen != (int)ON::OPTIONS && state->screen_last != (int)ON::OPTIONS)
{
level_timers.clear();
script_input.clear();
clear_custom_shopitem_names();
}*/
if (g_state->screen != state.screen)
ScriptState& script_state = get_locals().state;
StateMemory* state = State::get().ptr();
if (state->screen != script_state.screen)
{
if (on_screen)
on_screen.value()();
}
if (on_frame && g_state->time_level != state.time_level && g_state->screen == (int)ON::LEVEL)
if (on_frame && state->time_level != script_state.time_level && state->screen == (int)ON::LEVEL)
{
on_frame.value()();
}
if (g_state->screen == (int)ON::CAMP && g_state->screen_last != (int)ON::OPTIONS && g_state->loading != state.loading && g_state->loading == 3 && g_state->time_level == 1)
if (state->screen == (int)ON::CAMP && state->screen_last != (int)ON::OPTIONS && state->loading != script_state.loading && state->loading == 3 && state->time_level == 1)
{
if (on_camp)
on_camp.value()();
}
if (g_state->screen == (int)ON::LEVEL && g_state->screen_last != (int)ON::OPTIONS && g_state->loading != state.loading && g_state->loading == 3 && g_state->time_level == 1)
if (state->screen == (int)ON::LEVEL && state->screen_last != (int)ON::OPTIONS && state->loading != script_state.loading && state->loading == 3 && state->time_level == 1)
{
if (g_state->level_count == 0)
if (state->level_count == 0)
{
if (on_start)
on_start.value()();
}
if (on_level)
on_level.value()();
}
if (g_state->screen == (int)ON::TRANSITION && state.screen != (int)ON::TRANSITION)
if (state->screen == (int)ON::TRANSITION && script_state.screen != (int)ON::TRANSITION)
{
if (on_transition)
on_transition.value()();
}
if (g_state->screen == (int)ON::DEATH && state.screen != (int)ON::DEATH)
if (state->screen == (int)ON::DEATH && script_state.screen != (int)ON::DEATH)
{
if (on_death)
on_death.value()();
}
if ((g_state->screen == (int)ON::WIN && state.screen != (int)ON::WIN) || (g_state->screen == (int)ON::CONSTELLATION && state.screen != (int)ON::CONSTELLATION))
if ((state->screen == (int)ON::WIN && script_state.screen != (int)ON::WIN) || (state->screen == (int)ON::CONSTELLATION && script_state.screen != (int)ON::CONSTELLATION))
{
if (on_win)
on_win.value()();
Expand Down Expand Up @@ -372,7 +383,7 @@ bool LuaBackend::update()

for (auto it = global_timers.begin(); it != global_timers.end();)
{
int now = get_frame_count();
int now = State::get_frame_count(state);
if (auto cb = std::get_if<IntervalCallback>(&it->second))
{
if (now >= cb->lastRan + cb->interval && !is_callback_cleared(it->first))
Expand Down Expand Up @@ -409,7 +420,7 @@ bool LuaBackend::update()
}
}

auto now = get_frame_count();
auto now = State::get_frame_count(state);
for (auto& [id, callback] : load_callbacks)
{
if (callback.lastRan < 0)
Expand All @@ -427,17 +438,17 @@ bool LuaBackend::update()
continue;

set_current_callback(-1, id, CallbackType::Normal);
if ((ON)g_state->screen == callback.screen && g_state->screen != state.screen && g_state->screen_last != (int)ON::OPTIONS) // game screens
if ((ON)state->screen == callback.screen && state->screen != script_state.screen && state->screen_last != (int)ON::OPTIONS) // game screens
{
handle_function<void>(this, callback.func);
callback.lastRan = now;
}
else if (callback.screen == ON::LEVEL && g_state->screen == (int)ON::LEVEL && g_state->screen_last != (int)ON::OPTIONS && state.loading != g_state->loading && g_state->loading == 3 && g_state->time_level <= 1)
else if (callback.screen == ON::LEVEL && state->screen == (int)ON::LEVEL && state->screen_last != (int)ON::OPTIONS && script_state.loading != state->loading && state->loading == 3 && state->time_level <= 1)
{
handle_function<void>(this, callback.func);
callback.lastRan = now;
}
else if (callback.screen == ON::CAMP && g_state->screen == (int)ON::CAMP && g_state->screen_last != (int)ON::OPTIONS && state.loading != g_state->loading && g_state->loading == 3 && g_state->time_level == 1)
else if (callback.screen == ON::CAMP && state->screen == (int)ON::CAMP && state->screen_last != (int)ON::OPTIONS && script_state.loading != state->loading && state->loading == 3 && state->time_level == 1)
{
handle_function<void>(this, callback.func);
callback.lastRan = now;
Expand All @@ -448,7 +459,7 @@ bool LuaBackend::update()
{
case ON::FRAME:
{
if (g_state->time_level != state.time_level && g_state->screen == (int)ON::LEVEL)
if (state->time_level != script_state.time_level && state->screen == (int)ON::LEVEL)
{
handle_function<void>(this, callback.func);
callback.lastRan = now;
Expand All @@ -457,8 +468,8 @@ bool LuaBackend::update()
}
case ON::GAMEFRAME:
{
if (!g_state->pause && get_frame_count() != state.time_global &&
((g_state->screen >= (int)ON::CAMP && g_state->screen <= (int)ON::DEATH) || g_state->screen == (int)ON::ARENA_MATCH))
if (!state->pause && State::get_frame_count(state) != script_state.time_global &&
((state->screen >= (int)ON::CAMP && state->screen <= (int)ON::DEATH) || state->screen == (int)ON::ARENA_MATCH))
{
handle_function<void>(this, callback.func);
callback.lastRan = now;
Expand All @@ -467,7 +478,7 @@ bool LuaBackend::update()
}
case ON::SCREEN:
{
if (g_state->screen != state.screen)
if (state->screen != script_state.screen)
{
handle_function<void>(this, callback.func);
callback.lastRan = now;
Expand All @@ -476,7 +487,7 @@ bool LuaBackend::update()
}
case ON::START:
{
if (g_state->screen == (int)ON::LEVEL && g_state->screen_last != (int)ON::OPTIONS && g_state->level_count == 0 && g_state->loading != state.loading && g_state->loading == 3 && g_state->time_level <= 1)
if (state->screen == (int)ON::LEVEL && state->screen_last != (int)ON::OPTIONS && state->level_count == 0 && state->loading != script_state.loading && state->loading == 3 && state->time_level <= 1)
{
handle_function<void>(this, callback.func);
callback.lastRan = now;
Expand All @@ -485,7 +496,7 @@ bool LuaBackend::update()
}
case ON::LOADING:
{
if (g_state->loading > 0 && g_state->loading != state.loading)
if (state->loading > 0 && state->loading != script_state.loading)
{
handle_function<void>(this, callback.func);
callback.lastRan = now;
Expand All @@ -494,7 +505,7 @@ bool LuaBackend::update()
}
case ON::RESET:
{
if ((g_state->quest_flags & 1) > 0 && (g_state->quest_flags & 1) != state.reset)
if ((state->quest_flags & 1) > 0 && (state->quest_flags & 1) != script_state.reset)
{
handle_function<void>(this, callback.func);
callback.lastRan = now;
Expand All @@ -507,7 +518,7 @@ bool LuaBackend::update()
}
clear_current_callback();
}
const int now_l = g_state->time_level;
const int now_l = state->time_level;
for (auto it = level_timers.begin(); it != level_timers.end();)
{
if (auto cb = std::get_if<IntervalCallback>(&it->second))
Expand Down Expand Up @@ -551,22 +562,22 @@ bool LuaBackend::update()
for (auto& [id, callback] : save_callbacks)
{
set_current_callback(-1, id, CallbackType::Normal);
if ((g_state->loading != state.loading && g_state->loading == 1) || manual_save)
if ((state->loading != script_state.loading && state->loading == 1) || manual_save)
{
handle_function<void>(this, callback.func, SaveContext{get_root(), get_name()});
callback.lastRan = now;
}
clear_current_callback();
}

state.screen = g_state->screen;
state.time_level = g_state->time_level;
state.time_total = g_state->time_total;
state.time_global = get_frame_count();
state.frame = get_frame_count();
state.loading = g_state->loading;
state.reset = (g_state->quest_flags & 1);
state.quest_flags = g_state->quest_flags;
script_state.screen = state->screen;
script_state.time_level = state->time_level;
script_state.time_total = state->time_total;
script_state.time_global = get_frame_count();
script_state.frame = get_frame_count();
script_state.loading = state->loading;
script_state.reset = (state->quest_flags & 1);
script_state.quest_flags = state->quest_flags;

if (manual_save)
{
Expand Down Expand Up @@ -1700,7 +1711,7 @@ void LuaBackend::set_error(std::string err)
*/
void LuaBackend::for_each_backend(std::function<bool(LockedBackend)> fun, bool stop_propagation)
{
std::lock_guard lock{g_all_backends_mutex};
std::lock_guard lock{global_lua_lock};
for (std::unique_ptr<ProtectedBackend>& backend : g_all_backends)
{
if (!fun(backend->Lock()) && stop_propagation)
Expand All @@ -1715,7 +1726,7 @@ LuaBackend::LockedBackend LuaBackend::get_backend(std::string_view id)
}
std::optional<LuaBackend::LockedBackend> LuaBackend::get_backend_safe(std::string_view id)
{
std::lock_guard lock{g_all_backends_mutex};
std::lock_guard lock{global_lua_lock};
for (std::unique_ptr<ProtectedBackend>& backend : g_all_backends)
{
LockedBackend locked = backend->Lock();
Expand All @@ -1732,7 +1743,7 @@ LuaBackend::LockedBackend LuaBackend::get_backend_by_id(std::string_view id, std
}
std::optional<LuaBackend::LockedBackend> LuaBackend::get_backend_by_id_safe(std::string_view id, std::string_view ver)
{
std::lock_guard lock{g_all_backends_mutex};
std::lock_guard lock{global_lua_lock};
for (std::unique_ptr<ProtectedBackend>& backend : g_all_backends)
{
LockedBackend locked = backend->Lock();
Expand Down Expand Up @@ -1866,6 +1877,42 @@ void LuaBackend::on_post(ON event)
}
}

void LuaBackend::copy_locals(StateMemory* from, StateMemory* to)
{
if (!local_state_datas.contains(from))
return;

auto& to_data = local_state_datas[to];
auto& from_data = local_state_datas[from];
to_data.state = from_data.state;
sol::object from_user_data = from_data.user_data;
if (from_user_data != sol::lua_nil)
{
to_data.user_data = (*vm)["deepcopy_object"](from_user_data);
}
}

void LuaBackend::pre_clone_heap(StateMemory* from, StateMemory* to)
{
if (!get_enabled())
return;

copy_locals(from, to);
auto now = get_frame_count();
for (auto& [id, callback] : callbacks)
{
if (is_callback_cleared(id))
continue;

if (callback.screen == ON::PRE_CLONE_HEAP)
{
set_current_callback(-1, id, CallbackType::Normal);
handle_function<void>(this, callback.func, from, to);
clear_current_callback();
callback.lastRan = now;
}
}
}
bool LuaBackend::pre_save_state(int slot, StateMemory* saved)
{
if (!get_enabled())
Expand Down
Loading
Loading