first working code
This commit is contained in:
commit
0b01cfc53b
76
.clang-format
Normal file
76
.clang-format
Normal 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
8
build_test.bat
Normal 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
397
rt_replay.h
Normal 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
|
Loading…
Reference in New Issue
Block a user