SRP icon indicating copy to clipboard operation
SRP copied to clipboard

Signals table in scheme storage does not persist across saves

Open Decane opened this issue 2 years ago • 3 comments

This can cause e.g. the main storyline to break per this Steam thread:

After inspecting the game's audio scripts, I can see that the sound_end signal does not persist across saves. Usually this isn't a problem, but it can be if a save gets created during the short time window after the object binder update() call that triggers the sound callback that registers the signal but before the subsequent object binder update() call that would trigger xr_logic.try_switch_to_another_section() and actually respond to that signal. If this happens, then upon loading the affected save, the signal will have been lost and any logic script that relies on it can get stuck.

The sound_end and theme_end signals may not be the only ones susceptible to being lost in the event of an unfortunately timed save.

Decane avatar Oct 15 '23 14:10 Decane

There is a work-around for processing the sound_end and theme_end signals specifically that does not require saving/loading the scheme storage signals table in xr_logic.script - and hence obviates introducing a new game requirement to fix the specific issue reported in the Steam thread.

Consider e.g. the update routine in bind_restrictor.script, which currently calls the active scheme's update routine before executing the update routine in xr_sound.script:

	if obj_st.active_scheme then
		xr_logic.issue_event(nil, obj_st[obj_st.active_scheme], "update", delta)
	end

	xr_sound.update(self.object:id())

If we instead flip the order of these statements, there should be no opportunity to save between the following two event chains:

  • xr_sound.update()active_sound:callback()obj_st[obj_st.active_scheme].signals["sound_end"] = true
  • obj_st[obj_st.active_scheme]:update()xr_logic.try_switch_to_another_section()

... provided the order is also flipped in every other relevant object binder. That's because all of the above code would then run within the same, single invocation of the object binder update routine instead of being spread across two invocations thereof, as previously (which allowed the possibility of a save happening between invocations).

Update: Implemented in https://github.com/Decane/SRP/commit/bcc6616e955049978e92c212bb8fe788852216b6.

Decane avatar Oct 15 '23 19:10 Decane

How often do object binder update() calls happen? Just interested in how long is the window for the save that breaks the logic.

SurDno avatar Oct 18 '23 01:10 SurDno

The object binder update() frequency for a space restrictor decreases with the restrictor's distance from the player. At the southern entrance to the Cordon where the level changer from the Swamps positions the player, I recorded the following real seconds elapsed since launching the game for the first 20 bind_restrictor update() instances for esc_space_restrictor_scene1_script (i.e. the space restrictor that binds the esc_quest_line.ltx logic that was discovered to be stuck in the Steam thread) - without moving:

  • 75.103
  • 75.964 (+0.861)
  • 76.283 (+0.319)
  • 76.565 (+0.282)
  • 76.859 (+0.294)
  • 77.172 (+0.313)
  • 77.464 (+0.292)
  • 77.767 (+0.303)
  • 78.067 (+0.300)
  • 78.355 (+0.288)
  • 78.639 (+0.284)
  • 78.923 (+0.284)
  • 79.223 (+0.300)
  • 79.519 (+0.296)
  • 79.808 (+0.289)
  • 80.186 (+0.378)
  • 80.469 (+0.283)
  • 80.756 (+0.287)
  • 81.039 (+0.283)
  • 81.336 (+0.297)

As you can see, if we ignore the first update after load (which is an outlier), the difference between the rest hovers at around 0.3 seconds. So there would be about a 0.3 second time window during which the storyline break reported in the Steam thread could happen.

However, as there are many instances of sound_end and theme_end signals in the game's logic scripts, the logic break reported in the Steam thread is just one of several possible similar logic breaks with the same root cause.

Decane avatar Oct 20 '23 19:10 Decane