commit 0b01cfc53b36a5e67512e498a40fbfd7c555cf8b Author: Kevin Trogant Date: Mon Jan 16 22:01:20 2023 +0100 first working code diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..afa3099 --- /dev/null +++ b/.clang-format @@ -0,0 +1,76 @@ +--- +AccessModifierOffset: '-4' +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: 'false' +AlignConsecutiveAssignments: 'true' +AlignConsecutiveDeclarations: 'false' +AlignEscapedNewlines: Right +AlignOperands: 'true' +AlignTrailingComments: 'true' +AllowAllArgumentsOnNextLine: 'false' +AllowAllConstructorInitializersOnNextLine: 'false' +AllowAllParametersOfDeclarationOnNextLine: 'false' +AllowShortBlocksOnASingleLine: 'false' +AllowShortCaseLabelsOnASingleLine: 'false' +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: 'false' +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: 'false' +AlwaysBreakTemplateDeclarations: 'Yes' +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: false + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: 'true' +BreakConstructorInitializers: BeforeColon +BreakStringLiterals: 'false' +ColumnLimit: '120' +CompactNamespaces: 'false' +ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' +FixNamespaceComments: 'false' +IndentCaseLabels: 'false' +IndentPPDirectives: BeforeHash +IndentWidth: '4' +IndentWrappedFunctionNames: 'false' +Language: Cpp +MaxEmptyLinesToKeep: '1' +NamespaceIndentation: None +PointerAlignment: Left +SortIncludes: 'false' +SpaceAfterCStyleCast: 'false' +SpaceAfterLogicalNot: 'false' +SpaceAfterTemplateKeyword: 'false' +SpaceBeforeAssignmentOperators: 'true' +SpaceBeforeCpp11BracedList: 'false' +SpaceBeforeCtorInitializerColon: 'true' +SpaceBeforeInheritanceColon: 'true' +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: 'true' +SpaceInEmptyParentheses: 'false' +SpacesInAngles: 'false' +SpacesInCStyleCastParentheses: 'false' +SpacesInContainerLiterals: 'true' +SpacesInParentheses: 'false' +SpacesInSquareBrackets: 'false' +Standard: Cpp11 +UseTab: Never +PenaltyReturnTypeOnItsOwnLine: 1000 +... diff --git a/build_test.bat b/build_test.bat new file mode 100644 index 0000000..e6f247a --- /dev/null +++ b/build_test.bat @@ -0,0 +1,8 @@ +@echo off + +mkdir build +pushd build + +clang -o test.exe -Wall -Wextra -Wpedantic -std=c99 -O0 -g ..\test.c + +popd \ No newline at end of file diff --git a/rt_replay.h b/rt_replay.h new file mode 100644 index 0000000..6e977e9 --- /dev/null +++ b/rt_replay.h @@ -0,0 +1,397 @@ +#ifndef RT_REPLAY_H +#define RT_REPLAY_H + +/* rt_replay.h + * Input replay system for game development. + * + * This library is C99 compliant and builds without warnings with + * (clang) -Wall -Wextra -Wpedantic. + * + * To integrate it into your codebase, define RT_REPLAY_IMPLEMENTATION + * before including it in one source file. + * By default, the library depends on stdint.h (for fixed size integers), + * and on stdlib.h (for malloc & free). + * You can provide replacements for these by defining macros as described + * below. + * + * It's provided under the terms of the MIT license (found below) + */ + +/* The MIT License (MIT) + * Copyright © 2023 Kevin Trogant + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Fixed size integers. + * + * You can replace these by defining the macros below. + * HOWEVER: Please note that this might break previous recordings if you + * inadvertently end up changing the size of the integers. + */ +#if !defined(RTR_INT32) || !defined(RTR_UINT32) || !defined(RTR_UINT64) + #include +#endif + +#if !defined(RTR_INT32) + #define RTR_INT32 int32_t +#endif + +#if !defined(RTR_UINT32) + #define RTR_UINT32 uint32_t +#endif + +#if !defined(RTR_UINT64) + #define RTR_UINT64 uint64_t +#endif + +typedef RTR_INT32 rtr_int32; +typedef RTR_UINT32 rtr_uint32; +typedef RTR_UINT64 rtr_uint64; + +/* Allocators. + * + * You can replace the allocation functions by defining RTR_MALLOC + * and RTR_FREE. Both take a void* context parameter, that is user defined + * and can be used by your functions however you need. + */ +#if !defined(RTR_MALLOC) && !defined(RTR_FREE) + + #include + #define RTR_MALLOC(ctx, sz) malloc(sz) + #define RTR_FREE(ctx, ptr) free(ptr) + +#elif !defined(RTR_MALLOC) || !defined(RTR_FREE) + #error "You must define RTR_MALLOC and RTR_FREE or none" +#endif + +/* Memcpy & memset + * + * You can replace these by defining the macros below. + */ +#if !defined(RTR_MEMCPY) || !defined(RTR_MEMSET) + #include +#endif + +#if !defined(RTR_MEMCPY) + #define RTR_MEMCPY(dst, src, n) memcpy((void*)(dst), (const void*)(src), (size_t)(n)) +#endif + +#if !defined(RTR_MEMSET) + #define RTR_MEMSET(dst, val, n) memset((void*)(dst), (int)(val), (size_t)(n)) +#endif + +enum +{ + RTR_SUCCESS, + RTR_ERROR_OUT_OF_MEMORY, + RTR_ERROR_NO_FRAME, + RTR_ERROR_INVALID_RECORDING, +}; + +enum +{ + RTR_RECORDING_MODE_RECORD, + RTR_RECORDING_MODE_PLAYBACK, +}; + +enum +{ + RTR_EVENT_TYPE_KEY, + RTR_EVENT_TYPE_ANALOG, +}; + +typedef struct rtr_record_frame rtr_record_frame; + +/* A recording keeps a list of record frames, where + * each frame contains a timestamp and a number of input events. + * + * During replay, events are fed back to the game once the timestamp is reached. + */ +typedef struct +{ + unsigned int mode; + double timestamp; + rtr_record_frame* frames; + rtr_record_frame* last_frame; + rtr_uint32 num_frames; + void* ctx; +} rtr_recording; + +typedef struct +{ + rtr_uint32 type; + union + { + struct + { + rtr_uint32 keycode; + rtr_uint32 state; + } key; + + struct + { + rtr_uint32 axis; + double value; + } analog; + } data; +} rtr_event; + +typedef struct +{ + rtr_record_frame* frame; + int is_finished; +} rtr_playback_cursor; + +/* + * Recording api + */ + +void rtr_begin_recording(void* user_ctx, rtr_recording* recording); + +int rtr_end_recording(rtr_recording* recording, void** buffer, rtr_uint64* buffer_size); + +int rtr_frame(rtr_recording* recording, double delta); + +int rtr_add_key_event(rtr_recording* recording, rtr_uint32 key, rtr_uint32 state); + +int rtr_add_analog_event(rtr_recording* recording, rtr_uint32 axis, double value); + +/* + * Playback api + */ + +int rtr_open_recording(void* buffer, rtr_uint64 buffer_size, void* user_ctx, rtr_recording* recording); + +void rtr_close_recording(rtr_recording* recording); + +void rtr_playback_frame(rtr_recording* recording, double delta); + +int rtr_get_events(rtr_recording* recording, unsigned int* num_events, rtr_event** events); + +#endif + +#define RT_REPLAY_IMPLEMENTATION +#ifdef RT_REPLAY_IMPLEMENTATION +#undef RT_REPLAY_IMPLEMENTATION + +#include + +#define RTR_UNUSED(x) (void)(sizeof(x)) +#define RTR_EVENTS_PER_FRAME 256 + +struct rtr_record_frame +{ + double timestamp; + rtr_record_frame* next; + rtr_uint32 num_events; + rtr_event events[RTR_EVENTS_PER_FRAME]; +}; + +#define RTR_MAKE_VERSION(major, minor, patch) (\ + (((major) & 0xfff) << 20) | \ + (((minor) & 0xfff) << 8) | \ + ((patch) & 0xff)) + +#define RTR_VERSION RTR_MAKE_VERSION(0, 1, 0) + +typedef struct +{ + rtr_uint32 version; + rtr_uint32 num_frames; +} rtr_buffer_header; + +typedef struct +{ + double rel_timestamp; + rtr_uint32 num_events; + rtr_event events[RTR_EVENTS_PER_FRAME]; +} rtr_buffer_frame; + +void rtr_begin_recording(void* user_ctx, rtr_recording* recording) +{ + rtr_recording rec; + rec.mode = RTR_RECORDING_MODE_RECORD; + rec.timestamp = 0.0; + rec.num_frames = 0; + rec.frames = NULL; + rec.ctx = user_ctx; + + *recording = rec; +} + +int rtr_end_recording(rtr_recording* recording, void** buffer, rtr_uint64* buffer_size) +{ + if (recording->mode != RTR_RECORDING_MODE_RECORD) + return RTR_ERROR_INVALID_RECORDING; + + /* Count the frames */ + rtr_int32 unseen_frames = (rtr_int32)recording->num_frames; + + for (rtr_record_frame* frame = recording->frames; frame != NULL; frame = frame->next) { + --unseen_frames; + } + if (unseen_frames != 0) + return RTR_ERROR_INVALID_RECORDING; + + rtr_uint64 req_size = sizeof(rtr_buffer_header) + sizeof(rtr_buffer_frame) * recording->num_frames; + + char* buf = RTR_MALLOC(recording->ctx, req_size); + if (!buf) + return RTR_ERROR_OUT_OF_MEMORY; + + rtr_buffer_header* hdr = (rtr_buffer_header*)buf; + hdr->version = RTR_VERSION; + hdr->num_frames = recording->num_frames; + rtr_buffer_frame* buf_frames = (rtr_buffer_frame*)(hdr + 1); + + int i = 0; + rtr_record_frame* frame = recording->frames; + while (frame) { + buf_frames[i].rel_timestamp = frame->timestamp; + buf_frames[i].num_events = frame->num_events; + RTR_MEMCPY(buf_frames[i].events, frame->events, sizeof(rtr_event) * frame->num_events); + + rtr_record_frame* tmp = frame; + frame = frame->next; + + RTR_FREE(recording->ctx, tmp); + } + + RTR_MEMSET(recording, 0, sizeof(recording)); + + *buffer = (void*)buf; + *buffer_size = req_size; + + return RTR_SUCCESS; +} + +int rtr_frame(rtr_recording* recording, double delta) +{ + rtr_record_frame* frame = (rtr_record_frame*)RTR_MALLOC(recording->ctx, sizeof(rtr_record_frame)); + if (!frame) { + return RTR_ERROR_OUT_OF_MEMORY; + } + recording->timestamp += delta; + frame->num_events = 0; + frame->next = NULL; + frame->timestamp = recording->timestamp; + + if (recording->last_frame) + recording->last_frame->next = frame; + recording->last_frame = frame; + ++recording->num_frames; + return RTR_SUCCESS; +} + +static int rtr_add_event(rtr_recording* recording, rtr_event event) +{ + if (!recording->last_frame) + return RTR_ERROR_NO_FRAME; + rtr_record_frame* frame = recording->last_frame; + + if (frame->num_events < RTR_EVENTS_PER_FRAME) { + frame->events[frame->num_events++] = event; + return RTR_SUCCESS; + } + else { + /* add a new "extended" frame with the same timestamp */ + rtr_record_frame* ext_frame = (rtr_record_frame*)RTR_MALLOC(recording->ctx, sizeof(rtr_record_frame)); + if (!ext_frame) + return RTR_ERROR_OUT_OF_MEMORY; + ext_frame->timestamp = frame->timestamp; + ext_frame->next = NULL; + ext_frame->events[0] = event; + ext_frame->num_events = 1; + + frame->next = ext_frame; + recording->last_frame = ext_frame; + return RTR_SUCCESS; + } +} + +int rtr_add_key_event(rtr_recording* recording, rtr_uint32 key, rtr_uint32 state) +{ + rtr_event event; + event.type = RTR_EVENT_TYPE_KEY; + event.data.key.keycode = key; + event.data.key.state = state; + return rtr_add_event(recording, event); +} + +int rtr_add_analog_event(rtr_recording* recording, rtr_uint32 axis, double value) +{ + rtr_event event; + event.type = RTR_EVENT_TYPE_ANALOG; + event.data.analog.axis = axis; + event.data.analog.value = value; + return rtr_add_event(recording, event); +} + +int rtr_open_recording(void* buffer, rtr_uint64 buffer_size, void* user_ctx, rtr_recording* recording) +{ + rtr_buffer_header* hdr = (rtr_buffer_header*)buffer; + if (hdr->version != RTR_VERSION) { + /* because this is the only valid version, this check suffices. + * in the future, we would check if the buffer contains a version + * we recognize */ + return RTR_ERROR_INVALID_RECORDING; + } + + if (((hdr->num_frames * sizeof(rtr_buffer_frame)) + sizeof(hdr)) > buffer_size) { + /* something went wrong */ + return RTR_ERROR_INVALID_RECORDING; + } + + rtr_buffer_frame* buf_frames = (rtr_buffer_frame*)(hdr + 1); + + rtr_record_frame* frames = RTR_MALLOC(user_ctx, sizeof(rtr_record_frame) * hdr->num_frames); + for (unsigned int i = 0; i < hdr->num_frames; ++i) { + frames[i].next = (i < hdr->num_frames - 1) ? &(frames[i + 1]) : NULL; + frames[i].num_events = buf_frames[i].num_events; + frames[i].timestamp = buf_frames[i].rel_timestamp; + RTR_MEMCPY(frames[i].events, buf_frames[i].events, buf_frames[i].num_events * sizeof(rtr_event)); + } + + recording->frames = frames; + recording->last_frame = &frames[hdr->num_frames - 1]; + recording->mode = RTR_RECORDING_MODE_PLAYBACK; + recording->num_frames = hdr->num_frames; + recording->timestamp = 0.0; + recording->ctx = user_ctx; + + return RTR_SUCCESS; +} + +void rtr_close_recording(rtr_recording* recording) +{ + if (recording->mode != RTR_RECORDING_MODE_PLAYBACK) + return; + RTR_FREE(recording->ctx, recording->frames); + RTR_MEMSET(recording, 0, sizeof(recording)); +} + +void rtr_playback_frame(rtr_recording* recording, double delta) +{ + +} + +int rtr_get_events(rtr_recording* recording, unsigned int* num_events, rtr_event** events); + +#endif diff --git a/test.c b/test.c new file mode 100644 index 0000000..b9e736f --- /dev/null +++ b/test.c @@ -0,0 +1,7 @@ +#define RT_REPLAY_IMPLEMENTATION +#include "rt_replay.h" + +int main() +{ + return 0; +}