Skip to content

Commit cca0103

Browse files
committedMay 13, 2021
Add rewind code from binjgb
1 parent bef653b commit cca0103

File tree

8 files changed

+863
-14
lines changed

8 files changed

+863
-14
lines changed
 

‎CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ if (NOT EMSCRIPTEN)
4040
src/host-gl.c
4141
src/host-ui-simple.c
4242
src/joypad.c
43+
src/rewind.c
4344
)
4445
target_link_libraries(binjnes SDL2::SDL2 SDL2::SDL2main ${OPENGL_gl_LIBRARY})
4546

@@ -68,6 +69,7 @@ else (EMSCRIPTEN)
6869
src/memory.c
6970
src/emulator.c
7071
src/joypad.c
72+
src/rewind.c
7173
src/emscripten/wrapper.c)
7274
set(EXPORTED_JSON ${PROJECT_SOURCE_DIR}/src/emscripten/exported.json)
7375
target_include_directories(binjnes PUBLIC ${PROJECT_SOURCE_DIR}/src)

‎src/binjnes.c

+39-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
// TODO: make these configurable?
1313
#define AUDIO_FREQUENCY 44100
1414
#define AUDIO_FRAMES 2048 /* ~46ms of latency at 44.1kHz */
15+
#define REWIND_FRAMES_PER_BASE_STATE 45
16+
#define REWIND_BUFFER_CAPACITY MEGABYTES(32)
17+
#define REWIND_CYCLES_PER_FRAME (89432 * 3 / 2) /* Rewind at 1.5x */
1518

1619
static struct Emulator* e;
1720
static struct Host* host;
@@ -22,18 +25,48 @@ static Bool s_running = TRUE;
2225
static Bool s_step_frame;
2326
static Bool s_paused;
2427
static f32 s_audio_volume = 0.5f;
28+
static Bool s_rewinding;
29+
static Ticks s_rewind_start;
2530
static u32 s_render_scale = 4;
2631

32+
static void begin_rewind(void) {
33+
if (!s_rewinding) {
34+
host_begin_rewind(host);
35+
s_rewinding = TRUE;
36+
s_rewind_start = emulator_get_ticks(e);
37+
}
38+
}
39+
40+
static void rewind_by(Ticks delta) {
41+
Ticks now = emulator_get_ticks(e);
42+
Ticks then = now;
43+
if (now >= delta) {
44+
then = now - delta;
45+
host_rewind_to_ticks(host, then);
46+
}
47+
}
48+
49+
static void end_rewind(void) {
50+
host_end_rewind(host);
51+
s_rewinding = FALSE;
52+
}
53+
2754
static void key_down(HostHookContext *ctx, HostKeycode code) {
2855
switch (code) {
2956
case HOST_KEYCODE_N: s_step_frame = TRUE; s_paused = FALSE; break;
3057
case HOST_KEYCODE_SPACE: s_paused ^= 1; break;
3158
case HOST_KEYCODE_ESCAPE: s_running = FALSE; break;
59+
case HOST_KEYCODE_BACKSPACE: begin_rewind(); break;
3260
default: break;
3361
}
3462
}
3563

36-
static void key_up(HostHookContext *ctx, HostKeycode code) {}
64+
static void key_up(HostHookContext *ctx, HostKeycode code) {
65+
switch (code) {
66+
case HOST_KEYCODE_BACKSPACE: end_rewind(); break;
67+
default: break;
68+
}
69+
}
3770

3871
int main(int argc, char **argv) {
3972
int result = 1;
@@ -60,12 +93,16 @@ int main(int argc, char **argv) {
6093
host_init.audio_frequency = AUDIO_FREQUENCY;
6194
host_init.audio_frames = AUDIO_FRAMES;
6295
host_init.audio_volume = s_audio_volume;
96+
host_init.rewind.frames_per_base_state = REWIND_FRAMES_PER_BASE_STATE;
97+
host_init.rewind.buffer_capacity = REWIND_BUFFER_CAPACITY;
6398
host = host_new(&host_init, e);
6499
CHECK(host != NULL);
65100

66101
f64 refresh_ms = host_get_monitor_refresh_ms(host);
67102
while (s_running && host_poll_events(host)) {
68-
if (!s_paused) {
103+
if (s_rewinding) {
104+
rewind_by(REWIND_CYCLES_PER_FRAME);
105+
} else if (!s_paused) {
69106
EmulatorEvent event = host_run_ms(host, refresh_ms);
70107
if (event & EMULATOR_EVENT_INVALID_OPCODE) {
71108
// set_status_text("invalid opcode!");

‎src/emulator.c

+59-11
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212

1313
#include "emulator.h"
1414

15+
#define SAVE_STATE_VERSION (0)
16+
#define SAVE_STATE_HEADER (u32)(0x23557a7e + SAVE_STATE_VERSION)
17+
1518
#define LOGLEVEL 0
1619
#define DISASM 0
1720

@@ -1216,8 +1219,8 @@ void cpu_write(E *e, u16 addr, u8 val) {
12161219
}
12171220
}
12181221

1219-
static void set_mirror(E *e, Mirror mirror) {
1220-
switch (mirror) {
1222+
static void update_nt_map(E* e) {
1223+
switch (e->s.p.mirror) {
12211224
case MIRROR_HORIZONTAL:
12221225
e->nt_map[0] = e->nt_map[1] = e->s.p.ram;
12231226
e->nt_map[2] = e->nt_map[3] = e->s.p.ram + 0x400;
@@ -1236,17 +1239,27 @@ static void set_mirror(E *e, Mirror mirror) {
12361239
}
12371240
}
12381241

1239-
static void set_chr1k_map(E *e, u8 bank0, u8 bank1, u8 bank2, u8 bank3,
1240-
u8 bank4, u8 bank5, u8 bank6, u8 bank7) {
1241-
u8 banks[] = {bank0, bank1, bank2, bank3, bank4, bank5, bank6, bank7};
1242-
for (size_t i = 0; i < ARRAY_SIZE(banks); ++i) {
1243-
u8 bank = banks[i] & (e->ci.chr1k_banks - 1);
1242+
static void set_mirror(E *e, Mirror mirror) {
1243+
e->s.p.mirror = mirror;
1244+
update_nt_map(e);
1245+
}
1246+
1247+
static void update_chr1k_map(E* e) {
1248+
for (size_t i = 0; i < ARRAY_SIZE(e->s.m.chr1k_bank); ++i) {
1249+
u8 bank = e->s.m.chr1k_bank[i] & (e->ci.chr1k_banks - 1);
12441250
e->chr_map[i] = e->ci.chr_data + (bank << 10);
12451251
// TODO: chr_data_write always points into chr_ram (which is fixed at 8KiB).
12461252
e->chr_map_write[i] = e->ci.chr_data_write + ((bank & 7) << 10);
12471253
}
12481254
}
12491255

1256+
static void set_chr1k_map(E *e, u8 bank0, u8 bank1, u8 bank2, u8 bank3,
1257+
u8 bank4, u8 bank5, u8 bank6, u8 bank7) {
1258+
u8 banks[] = {bank0, bank1, bank2, bank3, bank4, bank5, bank6, bank7};
1259+
memcpy(e->s.m.chr1k_bank, banks, sizeof(e->s.m.chr1k_bank));
1260+
update_chr1k_map(e);
1261+
}
1262+
12501263
static void set_chr4k_map(E *e, u8 bank0, u8 bank1) {
12511264
bank0 &= (e->ci.chr4k_banks - 1);
12521265
bank1 &= (e->ci.chr4k_banks - 1);
@@ -1259,14 +1272,19 @@ static void set_chr8k_map(E *e, u8 bank) {
12591272
set_chr4k_map(e, bank * 2, bank * 2 + 1);
12601273
}
12611274

1262-
static void set_prg8k_map(E *e, u8 bank0, u8 bank1, u8 bank2, u8 bank3) {
1263-
u8 banks[] = {bank0, bank1, bank2, bank3};
1264-
for (size_t i = 0; i < ARRAY_SIZE(banks); ++i) {
1265-
u8 bank = banks[i] & (e->ci.prg8k_banks - 1);
1275+
static void update_prg8k_map(E* e) {
1276+
for (size_t i = 0; i < ARRAY_SIZE(e->s.m.prg8k_bank); ++i) {
1277+
u8 bank = e->s.m.prg8k_bank[i] & (e->ci.prg8k_banks - 1);
12661278
e->prg_rom_map[i] = e->ci.prg_data + (bank << 13);
12671279
}
12681280
}
12691281

1282+
static void set_prg8k_map(E *e, u8 bank0, u8 bank1, u8 bank2, u8 bank3) {
1283+
u8 banks[] = {bank0, bank1, bank2, bank3};
1284+
memcpy(e->s.m.prg8k_bank, banks, sizeof(e->s.m.prg8k_bank));
1285+
update_prg8k_map(e);
1286+
}
1287+
12701288
static void set_prg16k_map(E *e, u8 bank0, u8 bank1) {
12711289
bank0 &= (e->ci.prg16k_banks - 1);
12721290
bank1 &= (e->ci.prg16k_banks - 1);
@@ -2328,6 +2346,36 @@ EEvent emulator_run_until(E *e, Ticks until_ticks) {
23282346
return e->s.event;
23292347
}
23302348

2349+
void emulator_init_state_file_data(FileData* file_data) {
2350+
file_data->size = sizeof(S);
2351+
file_data->data = xmalloc(file_data->size);
2352+
}
2353+
2354+
Result emulator_read_state(Emulator *e, const FileData *file_data) {
2355+
CHECK_MSG(file_data->size == sizeof(S),
2356+
"save state file is wrong size: %ld, expected %ld.\n",
2357+
(long)file_data->size, (long)sizeof(S));
2358+
S *new_state = (S *)file_data->data;
2359+
CHECK_MSG(new_state->header == SAVE_STATE_HEADER,
2360+
"header mismatch: %u, expected %u.\n", new_state->header,
2361+
SAVE_STATE_HEADER);
2362+
memcpy(&e->s, new_state, sizeof(S));
2363+
// Fix pointers.
2364+
update_chr1k_map(e);
2365+
update_prg8k_map(e);
2366+
update_nt_map(e);
2367+
return OK;
2368+
ON_ERROR_RETURN;
2369+
}
2370+
2371+
Result emulator_write_state(Emulator *e, FileData *file_data) {
2372+
CHECK(file_data->size >= sizeof(S));
2373+
e->s.header = SAVE_STATE_HEADER;
2374+
memcpy(file_data->data, &e->s, file_data->size);
2375+
return OK;
2376+
ON_ERROR_RETURN;
2377+
}
2378+
23312379
// Debug stuff /////////////////////////////////////////////////////////////////
23322380

23332381
static const char* s_opcode_mnemonic[256];

‎src/emulator.h

+8-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ typedef struct {
8989
} CartInfo;
9090

9191
typedef struct {
92-
u8 chr_bank[6], prg_bank[2];
92+
u8 chr1k_bank[8], prg8k_bank[4]; // Actual mapped bank indexes.
93+
u8 chr_bank[6], prg_bank[2]; // Mapper's selected bank indexes.
9394
union {
9495
struct {
9596
u8 bits, data, ctrl;
@@ -133,6 +134,7 @@ typedef struct {
133134
u32 fbidx, bits_mask, frame;
134135
Spr spr;
135136
u64 read_status_cy, last_vram_access_cy, a12_low_count;
137+
Mirror mirror;
136138
} P;
137139

138140
typedef struct {
@@ -154,6 +156,7 @@ typedef struct {
154156
} J;
155157

156158
typedef struct {
159+
u32 header;
157160
u64 cy;
158161
EmulatorEvent event;
159162
C c;
@@ -187,6 +190,10 @@ AudioBuffer* emulator_get_audio_buffer(Emulator*);
187190
u32 audio_buffer_get_frames(AudioBuffer*);
188191
Ticks emulator_get_ticks(Emulator*);
189192

193+
void emulator_init_state_file_data(FileData*);
194+
Result emulator_read_state(Emulator*, const FileData*);
195+
Result emulator_write_state(Emulator*, FileData*);
196+
190197
EmulatorEvent emulator_step(Emulator*);
191198
EmulatorEvent emulator_run_until(Emulator*, Ticks until_ticks);
192199

‎src/host.c

+93
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include "host-gl.h"
1212
#include "host-ui.h"
13+
#include "rewind.h"
1314

1415
#define HOOK0(name) \
1516
do \
@@ -47,6 +48,12 @@ typedef struct {
4748
f32 volume; /* [0..1] */
4849
} Audio;
4950

51+
typedef struct {
52+
RewindResult rewind_result;
53+
JoypadPlayback joypad_playback;
54+
Bool rewinding;
55+
} RewindState;
56+
5057
typedef struct Host {
5158
HostInit init;
5259
HostConfig config;
@@ -60,6 +67,8 @@ typedef struct Host {
6067
struct HostUI* ui;
6168
HostTexture* fb_texture;
6269
JoypadBuffer* joypad_buffer;
70+
RewindBuffer* rewind_buffer;
71+
RewindState rewind_state;
6372
JoypadPlayback joypad_playback;
6473
Ticks last_ticks;
6574
Bool key_state[HOST_KEYCODE_COUNT];
@@ -291,11 +300,36 @@ Result host_write_joypad_to_file(struct Host* host, const char* filename) {
291300
return result;
292301
}
293302

303+
static void append_rewind_state(Host* host) {
304+
if (host->rewind_state.rewinding) {
305+
return;
306+
}
307+
308+
rewind_append(host->rewind_buffer, host_get_emulator(host));
309+
}
310+
311+
Ticks host_get_rewind_oldest_ticks(struct Host* host) {
312+
return rewind_get_oldest_ticks(host->rewind_buffer);
313+
}
314+
315+
Ticks host_get_rewind_newest_ticks(struct Host* host) {
316+
return rewind_get_newest_ticks(host->rewind_buffer);
317+
}
318+
319+
JoypadStats host_get_joypad_stats(struct Host* host) {
320+
return joypad_get_stats(host->joypad_buffer);
321+
}
322+
323+
RewindStats host_get_rewind_stats(struct Host* host) {
324+
return rewind_get_stats(host->rewind_buffer);
325+
}
326+
294327
static void host_handle_event(Host* host, EmulatorEvent event) {
295328
Emulator* e = host_get_emulator(host);
296329
if (event & EMULATOR_EVENT_NEW_FRAME) {
297330
host_upload_texture(host, host->fb_texture, SCREEN_WIDTH, SCREEN_HEIGHT,
298331
*emulator_get_frame_buffer(e));
332+
append_rewind_state(host);
299333
}
300334
if (event & EMULATOR_EVENT_AUDIO_BUFFER_FULL) {
301335
host_render_audio(host);
@@ -315,6 +349,60 @@ static EmulatorEvent host_run_until_ticks(struct Host* host, Ticks ticks) {
315349
return event;
316350
}
317351

352+
void host_begin_rewind(Host* host) {
353+
assert(!host->rewind_state.rewinding);
354+
host->rewind_state.rewinding = TRUE;
355+
}
356+
357+
Result host_rewind_to_ticks(Host* host, Ticks ticks) {
358+
assert(host->rewind_state.rewinding);
359+
360+
RewindResult* result = &host->rewind_state.rewind_result;
361+
CHECK(SUCCESS(rewind_to_ticks(host->rewind_buffer, ticks, result)));
362+
363+
Emulator* e = host_get_emulator(host);
364+
CHECK(SUCCESS(emulator_read_state(e, &result->file_data)));
365+
assert(emulator_get_ticks(e) == result->info->ticks);
366+
367+
if (emulator_get_ticks(e) < ticks) {
368+
/* Save old joypad callback. */
369+
JoypadCallbackInfo old_jci = emulator_get_joypad_callback(e);
370+
emulator_set_joypad_playback_callback(e, host->joypad_buffer,
371+
&host->rewind_state.joypad_playback);
372+
host_run_until_ticks(host, ticks);
373+
/* Restore old joypad callback. */
374+
emulator_set_joypad_callback(e, old_jci.callback, old_jci.user_data);
375+
}
376+
377+
return OK;
378+
ON_ERROR_RETURN;
379+
}
380+
381+
void host_end_rewind(Host* host) {
382+
Ticks ticks = emulator_get_ticks(host_get_emulator(host));
383+
assert(host->rewind_state.rewinding);
384+
385+
if (host->rewind_state.rewind_result.info) {
386+
Emulator* e = host_get_emulator(host);
387+
rewind_truncate_to(host->rewind_buffer, e,
388+
&host->rewind_state.rewind_result);
389+
if (!host->init.joypad_filename) {
390+
joypad_truncate_to(host->joypad_buffer,
391+
host->rewind_state.joypad_playback.current);
392+
/* Append the current joypad state. */
393+
JoypadButtons buttons;
394+
joypad_callback(&buttons, host);
395+
}
396+
host->last_ticks = emulator_get_ticks(e);
397+
}
398+
399+
memset(&host->rewind_state, 0, sizeof(host->rewind_state));
400+
}
401+
402+
Bool host_is_rewinding(Host* host) {
403+
return host->rewind_state.rewinding;
404+
}
405+
318406
Result host_init(Host* host, Emulator* e) {
319407
CHECK_MSG(
320408
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER) == 0,
@@ -323,12 +411,14 @@ Result host_init(Host* host, Emulator* e) {
323411
CHECK(SUCCESS(host_init_video(host)));
324412
CHECK(SUCCESS(host_init_audio(host)));
325413
host_init_joypad(host, e);
414+
host->rewind_buffer = rewind_new(&host->init.rewind, e);
326415
host->last_ticks = emulator_get_ticks(e);
327416
return OK;
328417
ON_ERROR_RETURN;
329418
}
330419

331420
EmulatorEvent host_run_ms(struct Host* host, f64 delta_ms) {
421+
assert(!host->rewind_state.rewinding);
332422
Emulator* e = host_get_emulator(host);
333423
Ticks delta_ticks = (Ticks)(delta_ms * PPU_TICKS_PER_SECOND / 1000);
334424
Ticks until_ticks = emulator_get_ticks(e) + delta_ticks;
@@ -338,6 +428,7 @@ EmulatorEvent host_run_ms(struct Host* host, f64 delta_ms) {
338428
}
339429

340430
EmulatorEvent host_step(Host* host) {
431+
assert(!host->rewind_state.rewinding);
341432
Emulator* e = host_get_emulator(host);
342433
EmulatorEvent event = emulator_step(e);
343434
host_handle_event(host, event);
@@ -364,6 +455,8 @@ void host_delete(Host* host) {
364455
SDL_GL_DeleteContext(host->gl_context);
365456
SDL_DestroyWindow(host->window);
366457
SDL_Quit();
458+
joypad_delete(host->joypad_buffer);
459+
rewind_delete(host->rewind_buffer);
367460
xfree(host->audio.buffer);
368461
xfree(host);
369462
}

‎src/host.h

+19
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "common.h"
1111
#include "emulator.h"
1212
#include "joypad.h"
13+
#include "rewind.h"
1314

1415
#ifdef __cplusplus
1516
extern "C" {
@@ -124,6 +125,7 @@ typedef struct HostInit {
124125
int audio_frequency;
125126
int audio_frames;
126127
f32 audio_volume;
128+
RewindInit rewind;
127129
const char* joypad_filename;
128130
} HostInit;
129131

@@ -162,6 +164,23 @@ void host_set_palette(struct Host*, RGBA palette[4]);
162164
void host_enable_palette(struct Host*, Bool enabled);
163165
void host_render_screen_overlay(struct Host*, struct HostTexture*);
164166

167+
/* Rewind support. */
168+
169+
Ticks host_oldest_ticks(struct Host*);
170+
Ticks host_newest_ticks(struct Host*);
171+
172+
Ticks host_get_rewind_oldest_ticks(struct Host*);
173+
Ticks host_get_rewind_newest_ticks(struct Host*);
174+
JoypadStats host_get_joypad_stats(struct Host*);
175+
RewindStats host_get_rewind_stats(struct Host*);
176+
177+
Result host_write_joypad_to_file(struct Host*, const char* filename);
178+
179+
void host_begin_rewind(struct Host*);
180+
Result host_rewind_to_ticks(struct Host*, Ticks ticks);
181+
void host_end_rewind(struct Host*);
182+
Bool host_is_rewinding(struct Host*);
183+
165184
HostTexture* host_get_frame_buffer_texture(struct Host*);
166185
HostTexture* host_create_texture(struct Host*, int w, int h, HostTextureFormat);
167186
void host_upload_texture(struct Host*, HostTexture*, int w, int h,

‎src/rewind.c

+527
Large diffs are not rendered by default.

‎src/rewind.h

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright (C) 2018 Ben Smith
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
7+
#ifndef BINJNES_REWIND_H_
8+
#define BINJNES_REWIND_H_
9+
10+
#include "common.h"
11+
12+
struct Emulator;
13+
14+
#ifdef __cplusplus
15+
extern "C" {
16+
#endif
17+
18+
typedef enum {
19+
RewindInfoKind_Base,
20+
RewindInfoKind_Diff,
21+
} RewindInfoKind;
22+
23+
typedef struct {
24+
Ticks ticks;
25+
u8* data;
26+
size_t size;
27+
RewindInfoKind kind;
28+
} RewindInfo;
29+
30+
typedef struct {
31+
RewindInfo* begin; /* begin <= end; if begin == end range is empty. */
32+
RewindInfo* end; /* end is exclusive. */
33+
} RewindInfoRange;
34+
35+
typedef struct {
36+
u8* begin;
37+
u8* end;
38+
} RewindDataRange;
39+
40+
typedef struct {
41+
int info_range_index;
42+
RewindInfo* info;
43+
FileData file_data;
44+
} RewindResult;
45+
46+
typedef struct {
47+
size_t buffer_capacity;
48+
int frames_per_base_state;
49+
} RewindInit;
50+
51+
typedef struct RewindBuffer {
52+
/*
53+
* | rewind buffer |
54+
* | |
55+
* | dr[0] | ... | dr[1] | ....... | ir[1] | ... | ir[0] |
56+
*
57+
* (dr == data_range, ir == info_range)
58+
*
59+
* All RewindInfo in ir[0] has a corresponding data range in dr[0]. Similarly,
60+
* all RewindInfo in ir[1] has a corresponding data range in dr[1].
61+
*
62+
* When new data is written, ir[0].begin moves left, which my overwrite old
63+
* data in ir[1]. The data is written after dr[0].end. If the newly written
64+
* data overlaps the beginning of dr[1], the associated RewindInfo in ir[1] is
65+
* removed.
66+
*
67+
* When ir[1].begin and dr[1].end cross, the buffer is filled. At this point,
68+
* dr[0] is moved to dr[1], and ir[0] is moved to ir[1]. (Just the pointers
69+
* move, not the actual data). Then dr[0] is reset to an empty data range and
70+
* ir[0] is reset to an empty info range.
71+
*
72+
* TODO(binji):: you can always recalculate the data ranges from the
73+
* information in the info ranges. Remove?
74+
*
75+
*/
76+
RewindInit init;
77+
RewindDataRange data_range[2];
78+
RewindInfoRange info_range[2];
79+
FileData last_state;
80+
FileData last_base_state;
81+
Ticks last_base_state_ticks;
82+
int frames_until_next_base;
83+
84+
/* Data is decompressed into these states when rewinding. */
85+
FileData rewind_diff_state;
86+
87+
/* Stats */
88+
size_t total_kind_bytes[2];
89+
size_t total_uncompressed_bytes;
90+
} RewindBuffer;
91+
92+
typedef struct {
93+
size_t base_bytes;
94+
size_t diff_bytes;
95+
size_t uncompressed_bytes;
96+
size_t used_bytes;
97+
size_t capacity_bytes;
98+
99+
size_t data_ranges[4];
100+
size_t info_ranges[4];
101+
} RewindStats;
102+
103+
RewindBuffer* rewind_new(const RewindInit*, struct Emulator*);
104+
void rewind_delete(RewindBuffer*);
105+
void rewind_append(RewindBuffer*, struct Emulator*);
106+
Result rewind_to_ticks(RewindBuffer*, Ticks, RewindResult*);
107+
void rewind_truncate_to(RewindBuffer*, struct Emulator*, RewindResult*);
108+
Ticks rewind_get_oldest_ticks(RewindBuffer*);
109+
Ticks rewind_get_newest_ticks(RewindBuffer*);
110+
RewindStats rewind_get_stats(RewindBuffer*);
111+
112+
#ifdef __cplusplus
113+
}
114+
#endif
115+
116+
#endif /* BINJNES_REWIND_H_ */

0 commit comments

Comments
 (0)
Please sign in to comment.