Skip to content

Commit 8ca4058

Browse files
authored
Add runahead support to GameSession (#45)
* Add runahead support to GameSession * Rename _runahead_base_frame to _runahead_start_frame * Fix runahead correctness issues * Fix local input frame and add runtime delay/runahead controls to online example * Minimize runahead footprint in existing code Move running_ahead flag to InputBuffer state via SetRunaheadMode() instead of threading it through GetCurrentInputs/GetInput parameters. Reset sync frame immediately inside HandleRunahead so AddLocalInput and network logic always see the real frame, removing the need for GetSessionFrame().
1 parent 7f1f19e commit 8ca4058

14 files changed

Lines changed: 199 additions & 10 deletions

File tree

Examples/OnlineSession/OnlineSession.cpp

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ int main(int argc, char* argv[]) {
109109
gekko_start(session, &config);
110110
gekko_net_adapter_set(session, gekko_default_adapter(ports[local_players[0]]));
111111

112+
gekko_set_runahead(session, 8);
113+
112114
int remote_handle = -1;
113115
for (int i = 0; i < num_players; i++) {
114116
bool is_local = false;
@@ -121,7 +123,7 @@ int main(int argc, char* argv[]) {
121123

122124
if (is_local) {
123125
gekko_add_actor(session, GekkoLocalPlayer, nullptr);
124-
gekko_set_local_delay(session, i, 1);
126+
gekko_set_local_delay(session, i, 10);
125127
}
126128
else {
127129
GekkoNetAddress addr = {};
@@ -137,6 +139,9 @@ int main(int argc, char* argv[]) {
137139
Gamestate gs = {};
138140
gs.Init(num_players);
139141

142+
unsigned char current_delay = 10;
143+
unsigned char current_runahead = 8;
144+
140145
bool running = true;
141146
while (running) {
142147
frame_start = SDL_GetPerformanceCounter();
@@ -147,6 +152,42 @@ int main(int argc, char* argv[]) {
147152
if (event.type == SDL_EVENT_QUIT) {
148153
running = false;
149154
}
155+
if (event.type == SDL_EVENT_KEY_DOWN) {
156+
switch (event.key.key) {
157+
case SDLK_F1:
158+
if (current_delay > 0) {
159+
current_delay--;
160+
for (int i = 0; i < num_local_players; i++) {
161+
gekko_set_local_delay(session, local_players[i], current_delay);
162+
}
163+
printf("delay: %d\n", current_delay);
164+
}
165+
break;
166+
case SDLK_F2:
167+
if (current_delay < 15) {
168+
current_delay++;
169+
for (int i = 0; i < num_local_players; i++) {
170+
gekko_set_local_delay(session, local_players[i], current_delay);
171+
}
172+
printf("delay: %d\n", current_delay);
173+
}
174+
break;
175+
case SDLK_F3:
176+
if (current_runahead > 0) {
177+
current_runahead--;
178+
gekko_set_runahead(session, current_runahead);
179+
printf("runahead: %d\n", current_runahead);
180+
}
181+
break;
182+
case SDLK_F4:
183+
if (current_runahead < 15) {
184+
current_runahead++;
185+
gekko_set_runahead(session, current_runahead);
186+
printf("runahead: %d\n", current_runahead);
187+
}
188+
break;
189+
}
190+
}
150191
}
151192

152193
auto local_input = gs.PollInput();
@@ -222,9 +263,10 @@ int main(int argc, char* argv[]) {
222263
gekko_network_stats(session, remote_handle, &netstats);
223264
char title[256];
224265
snprintf(title, sizeof(title),
225-
"Pong | P: %ums PA: %.1fms J: %.1fms | S: %.2f R: %.2f KB/s | FA: %.2f",
266+
"Pong | P: %ums PA: %.1fms J: %.1fms | S: %.2f R: %.2f KB/s | FA: %.2f | D: %d RA: %d",
226267
netstats.last_ping, netstats.avg_ping, netstats.jitter,
227-
netstats.kb_sent, netstats.kb_received, frames_ahead);
268+
netstats.kb_sent, netstats.kb_received, frames_ahead,
269+
current_delay, current_runahead);
228270
SDL_SetWindowTitle(window, title);
229271
}
230272

GekkoLib/include/event.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,16 @@ namespace Gekko {
3535
public:
3636
void Init(u32 input_size);
3737

38-
bool AddAdvanceEvent(SyncSystem& sync, bool rolling_back);
38+
bool AddAdvanceEvent(SyncSystem& sync, bool rolling_back, bool running_ahead = false);
3939

4040
void AddSaveEvent(SyncSystem& sync, StateStorage& storage, Frame* last_saved_frame = nullptr);
4141

4242
void AddLoadEvent(SyncSystem& sync, StateStorage& storage);
4343

44+
void AddRunaheadSaveEvent(SyncSystem& sync, StateStorage& storage);
45+
46+
void AddRunaheadLoadEvent(StateStorage& storage);
47+
4448
std::vector<GekkoGameEvent*>& GetEvents();
4549

4650
void Reset();

GekkoLib/include/gekkonet.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ typedef struct GekkoGameEvent {
118118
unsigned int input_len;
119119
unsigned char* inputs;
120120
bool rolling_back;
121+
bool running_ahead;
121122
} adv;
122123
struct GekkoSave {
123124
int frame;
@@ -189,6 +190,8 @@ GEKKONET_API int gekko_add_actor(GekkoSession* session, GekkoPlayerType player_t
189190

190191
GEKKONET_API void gekko_set_local_delay(GekkoSession* session, int player, unsigned char delay);
191192

193+
GEKKONET_API void gekko_set_runahead(GekkoSession* session, unsigned char runahead);
194+
192195
GEKKONET_API void gekko_add_local_input(GekkoSession* session, int player, void* input);
193196

194197
GEKKONET_API GekkoGameEvent** gekko_update_session(GekkoSession* session, int* count);

GekkoLib/include/input.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ namespace Gekko {
4848

4949
std::unique_ptr<GameInput> GetInput(Frame frame, bool prediction = false);
5050

51+
void SetRunaheadMode(bool running_ahead);
52+
5153
Frame GetLastReceivedFrame();
5254

5355
void ClearIncorrectFrames(Frame clear_limit);
@@ -62,6 +64,8 @@ namespace Gekko {
6264
bool CanPredictInput();
6365

6466
private:
67+
bool _running_ahead;
68+
6569
u8 _input_delay;
6670

6771
u8 _input_prediction_window;

GekkoLib/include/session.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
struct GekkoSession {
1616
virtual void Init(GekkoConfig* config) = 0;
1717
virtual void SetLocalDelay(i32 player, u8 delay) = 0;
18+
virtual void SetRunahead(u8 runahead) = 0;
1819
virtual void SetNetAdapter(GekkoNetAdapter* adapter) = 0;
1920
virtual i32 AddActor(GekkoPlayerType type, GekkoNetAddress* addr) = 0;
2021
virtual void AddLocalInput(i32 player, void* input) = 0;
@@ -36,6 +37,8 @@ namespace Gekko {
3637

3738
void SetLocalDelay(i32 player, u8 delay) override;
3839

40+
void SetRunahead(u8 runahead) override;
41+
3942
void SetNetAdapter(GekkoNetAdapter* adapter) override;
4043

4144
i32 AddActor(GekkoPlayerType type, GekkoNetAddress* addr) override;
@@ -75,6 +78,10 @@ namespace Gekko {
7578

7679
void HandleSavingConfirmedFrame();
7780

81+
void HandleRunahead();
82+
83+
void RewindRunahead();
84+
7885
void SendSessionHealthCheck();
7986

8087
void SendNetworkHealthCheck();
@@ -88,6 +95,10 @@ namespace Gekko {
8895

8996
Frame _last_sent_healthcheck;
9097

98+
Frame _runahead_start_frame;
99+
100+
u8 _runahead_frames;
101+
91102
std::unique_ptr<u8[]> _disconnected_input;
92103

93104
GekkoConfig _config;
@@ -111,6 +122,8 @@ namespace Gekko {
111122

112123
void SetLocalDelay(i32 player, u8 delay) override;
113124

125+
void SetRunahead(u8 runahead) override {}
126+
114127
void SetNetAdapter(GekkoNetAdapter* adapter) override;
115128

116129
i32 AddActor(GekkoPlayerType type, GekkoNetAddress* addr) override;
@@ -162,6 +175,8 @@ namespace Gekko {
162175

163176
void SetLocalDelay(i32 player, u8 delay) override;
164177

178+
void SetRunahead(u8 runahead) override {}
179+
165180
void SetNetAdapter(GekkoNetAdapter* adapter) override;
166181

167182
i32 AddActor(GekkoPlayerType type, GekkoNetAddress* addr) override;

GekkoLib/include/storage.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@ namespace Gekko {
2222

2323
StateEntry* GetState(Frame frame);
2424

25+
StateEntry* GetRunaheadState();
26+
2527
private:
2628
u32 _max_num_states;
2729

2830
std::vector<std::unique_ptr<StateEntry>> _states;
31+
32+
StateEntry _runahead_state;
2933
};
3034
}

GekkoLib/include/sync.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ namespace Gekko {
2121

2222
bool GetCurrentInputs(std::unique_ptr<u8[]>& inputs, Frame& frame);
2323

24+
void SetRunaheadMode(bool running_ahead);
25+
2426
bool GetSpectatorInputs(std::unique_ptr<u8[]>& inputs, Frame frame);
2527

2628
bool GetLocalInput(Handle player, std::unique_ptr<u8[]>& input, Frame frame);
@@ -31,7 +33,7 @@ namespace Gekko {
3133

3234
void SetInputPredictionWindow(Handle player, u8 input_window);
3335

34-
Frame GetCurrentFrame();
36+
Frame GetCurrentFrame() const;
3537

3638
void SetCurrentFrame(Frame frame);
3739

GekkoLib/src/event.cpp

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ void Gekko::GameEventSystem::Init(u32 input_size) {
171171
_current_events.clear();
172172
}
173173

174-
bool Gekko::GameEventSystem::AddAdvanceEvent(SyncSystem& sync, bool rolling_back)
174+
bool Gekko::GameEventSystem::AddAdvanceEvent(SyncSystem& sync, bool rolling_back, bool running_ahead)
175175
{
176176
Frame frame = GameInput::NULL_FRAME;
177177
std::unique_ptr<u8[]> inputs;
@@ -186,6 +186,7 @@ bool Gekko::GameEventSystem::AddAdvanceEvent(SyncSystem& sync, bool rolling_back
186186
event->type = GekkoAdvanceEvent;
187187
event->data.adv.frame = frame;
188188
event->data.adv.rolling_back = rolling_back;
189+
event->data.adv.running_ahead = running_ahead;
189190

190191
if (event->data.adv.inputs) {
191192
std::memcpy(event->data.adv.inputs, inputs.get(), event->data.adv.input_len);
@@ -199,7 +200,6 @@ void Gekko::GameEventSystem::AddSaveEvent(SyncSystem& sync, StateStorage& storag
199200
const Frame frame_to_save = sync.GetCurrentFrame();
200201

201202
auto state = storage.GetState(frame_to_save);
202-
203203
state->frame = frame_to_save;
204204

205205
_current_events.push_back(_event_buffer.GetEvent(false));
@@ -233,6 +233,38 @@ void Gekko::GameEventSystem::AddLoadEvent(SyncSystem& sync, StateStorage& storag
233233
event->data.load.state_len = state->state_len;
234234
}
235235

236+
void Gekko::GameEventSystem::AddRunaheadSaveEvent(SyncSystem& sync, StateStorage& storage)
237+
{
238+
const Frame frame_to_save = sync.GetCurrentFrame();
239+
240+
auto state = storage.GetRunaheadState();
241+
state->frame = frame_to_save;
242+
243+
_current_events.push_back(_event_buffer.GetEvent(false));
244+
245+
auto event = _current_events.back();
246+
event->type = GekkoSaveEvent;
247+
248+
event->data.save.frame = frame_to_save;
249+
event->data.save.state = state->state.get();
250+
event->data.save.checksum = &state->checksum;
251+
event->data.save.state_len = &state->state_len;
252+
}
253+
254+
void Gekko::GameEventSystem::AddRunaheadLoadEvent(StateStorage& storage)
255+
{
256+
auto state = storage.GetRunaheadState();
257+
258+
_current_events.push_back(_event_buffer.GetEvent(false));
259+
260+
auto event = _current_events.back();
261+
event->type = GekkoLoadEvent;
262+
263+
event->data.load.frame = state->frame;
264+
event->data.load.state = state->state.get();
265+
event->data.load.state_len = state->state_len;
266+
}
267+
236268
std::vector<GekkoGameEvent*>& Gekko::GameEventSystem::GetEvents()
237269
{
238270
return _current_events;

GekkoLib/src/game_session.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ Gekko::GameSession::GameSession()
99
_last_saved_frame = GameInput::NULL_FRAME - 1;
1010
_disconnected_input = nullptr;
1111
_last_sent_healthcheck = GameInput::NULL_FRAME;
12+
_runahead_start_frame = GameInput::NULL_FRAME;
13+
_runahead_frames = 0;
1214
_config = GekkoConfig();
1315
}
1416

@@ -41,6 +43,11 @@ void Gekko::GameSession::Init(GekkoConfig* config)
4143
_config.desync_detection = _config.limited_saving ? false : _config.desync_detection;
4244
}
4345

46+
void Gekko::GameSession::SetRunahead(u8 runahead)
47+
{
48+
_runahead_frames = runahead;
49+
}
50+
4451
void Gekko::GameSession::SetLocalDelay(i32 player, u8 delay)
4552
{
4653
for (u32 i = 0; i < _msg.locals.size(); i++) {
@@ -129,6 +136,9 @@ GekkoGameEvent** Gekko::GameSession::UpdateSession(i32* count)
129136
// add inputs so we can continue the session.
130137
AddDisconnectedPlayerInputs();
131138

139+
// rewind any runahead frames from the previous tick
140+
RewindRunahead();
141+
132142
// check if we need to rollback
133143
HandleRollback();
134144

@@ -150,6 +160,9 @@ GekkoGameEvent** Gekko::GameSession::UpdateSession(i32* count)
150160
}
151161
_sync.IncrementFrame();
152162
}
163+
164+
// run ahead if configured
165+
HandleRunahead();
153166
}
154167

155168
*count = _game_events.Count();
@@ -508,3 +521,35 @@ bool Gekko::GameSession::IsLockstepActive() const
508521
{
509522
return _config.input_prediction_window == 0;
510523
}
524+
525+
void Gekko::GameSession::RewindRunahead()
526+
{
527+
if (_runahead_start_frame == GameInput::NULL_FRAME) {
528+
return;
529+
}
530+
531+
_game_events.AddRunaheadLoadEvent(_storage);
532+
_runahead_start_frame = GameInput::NULL_FRAME;
533+
}
534+
535+
void Gekko::GameSession::HandleRunahead()
536+
{
537+
if (IsLockstepActive() || _runahead_frames == 0) {
538+
return;
539+
}
540+
541+
_runahead_start_frame = _sync.GetCurrentFrame();
542+
_game_events.AddRunaheadSaveEvent(_sync, _storage);
543+
544+
_sync.SetRunaheadMode(true);
545+
for (u8 i = 0; i < _runahead_frames; i++) {
546+
if (!_game_events.AddAdvanceEvent(_sync, false, true)) {
547+
break;
548+
}
549+
_sync.IncrementFrame();
550+
}
551+
_sync.SetRunaheadMode(false);
552+
553+
// Reset back to the real frame so AddLocalInput and network logic see the correct frame
554+
_sync.SetCurrentFrame(_runahead_start_frame);
555+
}

GekkoLib/src/gekkonet.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ void gekko_set_local_delay(GekkoSession* session, int player, unsigned char dela
5959
session->SetLocalDelay(player, delay);
6060
}
6161

62+
void gekko_set_runahead(GekkoSession* session, unsigned char runahead)
63+
{
64+
session->SetRunahead(runahead);
65+
}
66+
6267
void gekko_add_local_input(GekkoSession* session, int player, void* input)
6368
{
6469
session->AddLocalInput(player, input);

0 commit comments

Comments
 (0)