first working code

This commit is contained in:
Kevin Trogant 2023-01-16 22:01:20 +01:00
commit 0b01cfc53b
4 changed files with 488 additions and 0 deletions

76
.clang-format Normal file
View File

@ -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
...

8
build_test.bat Normal file
View File

@ -0,0 +1,8 @@
@echo off
mkdir build
pushd build
clang -o test.exe -Wall -Wextra -Wpedantic -std=c99 -O0 -g ..\test.c
popd

397
rt_replay.h Normal file
View File

@ -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 <stdint.h>
#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 <stdlib.h>
#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 <string.h>
#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 <stddef.h>
#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

7
test.c Normal file
View File

@ -0,0 +1,7 @@
#define RT_REPLAY_IMPLEMENTATION
#include "rt_replay.h"
int main()
{
return 0;
}