improve sw renderer
This commit is contained in:
parent
415d424057
commit
39ee0fefb7
89 changed files with 9380 additions and 2307 deletions
|
|
@ -6,7 +6,6 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
|
@ -14,6 +13,7 @@
|
|||
#include "pxl8_hal.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_macros.h"
|
||||
#include "pxl8_mem.h"
|
||||
#include "pxl8_repl.h"
|
||||
#include "pxl8_replay.h"
|
||||
#include "pxl8_script.h"
|
||||
|
|
@ -39,23 +39,23 @@ static void pxl8_audio_event_callback(u8 event_type, u8 context_id, u8 note, f32
|
|||
#endif
|
||||
|
||||
pxl8* pxl8_create(const pxl8_hal* hal) {
|
||||
pxl8* sys = (pxl8*)calloc(1, sizeof(pxl8));
|
||||
pxl8* sys = (pxl8*)pxl8_calloc(1, sizeof(pxl8));
|
||||
if (!sys) return NULL;
|
||||
|
||||
pxl8_log_init(&sys->log);
|
||||
|
||||
if (!hal) {
|
||||
pxl8_error("hal cannot be null");
|
||||
free(sys);
|
||||
pxl8_free(sys);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sys->hal = hal;
|
||||
|
||||
sys->game = (pxl8_game*)calloc(1, sizeof(pxl8_game));
|
||||
sys->game = (pxl8_game*)pxl8_calloc(1, sizeof(pxl8_game));
|
||||
if (!sys->game) {
|
||||
pxl8_error("failed to allocate game");
|
||||
free(sys);
|
||||
pxl8_free(sys);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -65,11 +65,11 @@ pxl8* pxl8_create(const pxl8_hal* hal) {
|
|||
void pxl8_destroy(pxl8* sys) {
|
||||
if (!sys) return;
|
||||
|
||||
if (sys->game) free(sys->game);
|
||||
if (sys->game) pxl8_free(sys->game);
|
||||
if (sys->cart) pxl8_cart_destroy(sys->cart);
|
||||
if (sys->hal && sys->platform_data) sys->hal->destroy(sys->platform_data);
|
||||
|
||||
free(sys);
|
||||
pxl8_free(sys);
|
||||
}
|
||||
|
||||
static void pxl8_print_help(void) {
|
||||
|
|
@ -171,7 +171,7 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
|
|||
cart_path = ".";
|
||||
} else {
|
||||
pxl8_error("no main.fnl or main.lua found in current directory");
|
||||
free(original_cwd);
|
||||
pxl8_free(original_cwd);
|
||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
}
|
||||
|
|
@ -190,7 +190,7 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
|
|||
pxl8_error("failed to load cart%s%s", load_from_path ? ": " : "", load_from_path ? cart_path : "");
|
||||
if (sys->cart) pxl8_cart_destroy(sys->cart);
|
||||
sys->cart = NULL;
|
||||
free(original_cwd);
|
||||
pxl8_free(original_cwd);
|
||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
|
|
@ -203,7 +203,7 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
|
|||
} else if (script_arg) {
|
||||
pxl8_strncpy(game->script_path, script_arg, sizeof(game->script_path));
|
||||
}
|
||||
free(original_cwd);
|
||||
pxl8_free(original_cwd);
|
||||
|
||||
const char* window_title = pxl8_cart_get_title(sys->cart);
|
||||
if (!window_title) window_title = "pxl8";
|
||||
|
|
@ -310,7 +310,7 @@ pxl8_result pxl8_update(pxl8* sys) {
|
|||
|
||||
if (pxl8_script_load_module(game->script, "pxl8") != PXL8_OK) {
|
||||
const char* err_msg = pxl8_script_get_last_error(game->script);
|
||||
pxl8_error("failed to setup pxl8 global: %s", err_msg);
|
||||
pxl8_error("failed to load pxl8 global: %s", err_msg);
|
||||
}
|
||||
|
||||
sys->repl = pxl8_repl_create();
|
||||
|
|
|
|||
|
|
@ -1,35 +1,35 @@
|
|||
#include "pxl8_bytes.h"
|
||||
#include <string.h>
|
||||
|
||||
void pxl8_pack_u8(u8* buf, size_t offset, u8 val) {
|
||||
void pxl8_pack_u8(u8* buf, usize offset, u8 val) {
|
||||
buf[offset] = val;
|
||||
}
|
||||
|
||||
void pxl8_pack_u16_le(u8* buf, size_t offset, u16 val) {
|
||||
void pxl8_pack_u16_le(u8* buf, usize offset, u16 val) {
|
||||
buf[offset] = (u8)(val);
|
||||
buf[offset + 1] = (u8)(val >> 8);
|
||||
}
|
||||
|
||||
void pxl8_pack_u16_be(u8* buf, size_t offset, u16 val) {
|
||||
void pxl8_pack_u16_be(u8* buf, usize offset, u16 val) {
|
||||
buf[offset] = (u8)(val >> 8);
|
||||
buf[offset + 1] = (u8)(val);
|
||||
}
|
||||
|
||||
void pxl8_pack_u32_le(u8* buf, size_t offset, u32 val) {
|
||||
void pxl8_pack_u32_le(u8* buf, usize offset, u32 val) {
|
||||
buf[offset] = (u8)(val);
|
||||
buf[offset + 1] = (u8)(val >> 8);
|
||||
buf[offset + 2] = (u8)(val >> 16);
|
||||
buf[offset + 3] = (u8)(val >> 24);
|
||||
}
|
||||
|
||||
void pxl8_pack_u32_be(u8* buf, size_t offset, u32 val) {
|
||||
void pxl8_pack_u32_be(u8* buf, usize offset, u32 val) {
|
||||
buf[offset] = (u8)(val >> 24);
|
||||
buf[offset + 1] = (u8)(val >> 16);
|
||||
buf[offset + 2] = (u8)(val >> 8);
|
||||
buf[offset + 3] = (u8)(val);
|
||||
}
|
||||
|
||||
void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val) {
|
||||
void pxl8_pack_u64_le(u8* buf, usize offset, u64 val) {
|
||||
buf[offset] = (u8)(val);
|
||||
buf[offset + 1] = (u8)(val >> 8);
|
||||
buf[offset + 2] = (u8)(val >> 16);
|
||||
|
|
@ -40,7 +40,7 @@ void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val) {
|
|||
buf[offset + 7] = (u8)(val >> 56);
|
||||
}
|
||||
|
||||
void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val) {
|
||||
void pxl8_pack_u64_be(u8* buf, usize offset, u64 val) {
|
||||
buf[offset] = (u8)(val >> 56);
|
||||
buf[offset + 1] = (u8)(val >> 48);
|
||||
buf[offset + 2] = (u8)(val >> 40);
|
||||
|
|
@ -51,85 +51,85 @@ void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val) {
|
|||
buf[offset + 7] = (u8)(val);
|
||||
}
|
||||
|
||||
void pxl8_pack_i8(u8* buf, size_t offset, i8 val) {
|
||||
void pxl8_pack_i8(u8* buf, usize offset, i8 val) {
|
||||
buf[offset] = (u8)val;
|
||||
}
|
||||
|
||||
void pxl8_pack_i16_le(u8* buf, size_t offset, i16 val) {
|
||||
void pxl8_pack_i16_le(u8* buf, usize offset, i16 val) {
|
||||
pxl8_pack_u16_le(buf, offset, (u16)val);
|
||||
}
|
||||
|
||||
void pxl8_pack_i16_be(u8* buf, size_t offset, i16 val) {
|
||||
void pxl8_pack_i16_be(u8* buf, usize offset, i16 val) {
|
||||
pxl8_pack_u16_be(buf, offset, (u16)val);
|
||||
}
|
||||
|
||||
void pxl8_pack_i32_le(u8* buf, size_t offset, i32 val) {
|
||||
void pxl8_pack_i32_le(u8* buf, usize offset, i32 val) {
|
||||
pxl8_pack_u32_le(buf, offset, (u32)val);
|
||||
}
|
||||
|
||||
void pxl8_pack_i32_be(u8* buf, size_t offset, i32 val) {
|
||||
void pxl8_pack_i32_be(u8* buf, usize offset, i32 val) {
|
||||
pxl8_pack_u32_be(buf, offset, (u32)val);
|
||||
}
|
||||
|
||||
void pxl8_pack_i64_le(u8* buf, size_t offset, i64 val) {
|
||||
void pxl8_pack_i64_le(u8* buf, usize offset, i64 val) {
|
||||
pxl8_pack_u64_le(buf, offset, (u64)val);
|
||||
}
|
||||
|
||||
void pxl8_pack_i64_be(u8* buf, size_t offset, i64 val) {
|
||||
void pxl8_pack_i64_be(u8* buf, usize offset, i64 val) {
|
||||
pxl8_pack_u64_be(buf, offset, (u64)val);
|
||||
}
|
||||
|
||||
void pxl8_pack_f32_le(u8* buf, size_t offset, f32 val) {
|
||||
void pxl8_pack_f32_le(u8* buf, usize offset, f32 val) {
|
||||
u32 bits;
|
||||
memcpy(&bits, &val, sizeof(bits));
|
||||
pxl8_pack_u32_le(buf, offset, bits);
|
||||
}
|
||||
|
||||
void pxl8_pack_f32_be(u8* buf, size_t offset, f32 val) {
|
||||
void pxl8_pack_f32_be(u8* buf, usize offset, f32 val) {
|
||||
u32 bits;
|
||||
memcpy(&bits, &val, sizeof(bits));
|
||||
pxl8_pack_u32_be(buf, offset, bits);
|
||||
}
|
||||
|
||||
void pxl8_pack_f64_le(u8* buf, size_t offset, f64 val) {
|
||||
void pxl8_pack_f64_le(u8* buf, usize offset, f64 val) {
|
||||
u64 bits;
|
||||
memcpy(&bits, &val, sizeof(bits));
|
||||
pxl8_pack_u64_le(buf, offset, bits);
|
||||
}
|
||||
|
||||
void pxl8_pack_f64_be(u8* buf, size_t offset, f64 val) {
|
||||
void pxl8_pack_f64_be(u8* buf, usize offset, f64 val) {
|
||||
u64 bits;
|
||||
memcpy(&bits, &val, sizeof(bits));
|
||||
pxl8_pack_u64_be(buf, offset, bits);
|
||||
}
|
||||
|
||||
u8 pxl8_unpack_u8(const u8* buf, size_t offset) {
|
||||
u8 pxl8_unpack_u8(const u8* buf, usize offset) {
|
||||
return buf[offset];
|
||||
}
|
||||
|
||||
u16 pxl8_unpack_u16_le(const u8* buf, size_t offset) {
|
||||
u16 pxl8_unpack_u16_le(const u8* buf, usize offset) {
|
||||
return (u16)buf[offset] | ((u16)buf[offset + 1] << 8);
|
||||
}
|
||||
|
||||
u16 pxl8_unpack_u16_be(const u8* buf, size_t offset) {
|
||||
u16 pxl8_unpack_u16_be(const u8* buf, usize offset) {
|
||||
return ((u16)buf[offset] << 8) | (u16)buf[offset + 1];
|
||||
}
|
||||
|
||||
u32 pxl8_unpack_u32_le(const u8* buf, size_t offset) {
|
||||
u32 pxl8_unpack_u32_le(const u8* buf, usize offset) {
|
||||
return (u32)buf[offset] |
|
||||
((u32)buf[offset + 1] << 8) |
|
||||
((u32)buf[offset + 2] << 16) |
|
||||
((u32)buf[offset + 3] << 24);
|
||||
}
|
||||
|
||||
u32 pxl8_unpack_u32_be(const u8* buf, size_t offset) {
|
||||
u32 pxl8_unpack_u32_be(const u8* buf, usize offset) {
|
||||
return ((u32)buf[offset] << 24) |
|
||||
((u32)buf[offset + 1] << 16) |
|
||||
((u32)buf[offset + 2] << 8) |
|
||||
(u32)buf[offset + 3];
|
||||
}
|
||||
|
||||
u64 pxl8_unpack_u64_le(const u8* buf, size_t offset) {
|
||||
u64 pxl8_unpack_u64_le(const u8* buf, usize offset) {
|
||||
return (u64)buf[offset] |
|
||||
((u64)buf[offset + 1] << 8) |
|
||||
((u64)buf[offset + 2] << 16) |
|
||||
|
|
@ -140,7 +140,7 @@ u64 pxl8_unpack_u64_le(const u8* buf, size_t offset) {
|
|||
((u64)buf[offset + 7] << 56);
|
||||
}
|
||||
|
||||
u64 pxl8_unpack_u64_be(const u8* buf, size_t offset) {
|
||||
u64 pxl8_unpack_u64_be(const u8* buf, usize offset) {
|
||||
return ((u64)buf[offset] << 56) |
|
||||
((u64)buf[offset + 1] << 48) |
|
||||
((u64)buf[offset + 2] << 40) |
|
||||
|
|
@ -151,56 +151,56 @@ u64 pxl8_unpack_u64_be(const u8* buf, size_t offset) {
|
|||
(u64)buf[offset + 7];
|
||||
}
|
||||
|
||||
i8 pxl8_unpack_i8(const u8* buf, size_t offset) {
|
||||
i8 pxl8_unpack_i8(const u8* buf, usize offset) {
|
||||
return (i8)buf[offset];
|
||||
}
|
||||
|
||||
i16 pxl8_unpack_i16_le(const u8* buf, size_t offset) {
|
||||
i16 pxl8_unpack_i16_le(const u8* buf, usize offset) {
|
||||
return (i16)pxl8_unpack_u16_le(buf, offset);
|
||||
}
|
||||
|
||||
i16 pxl8_unpack_i16_be(const u8* buf, size_t offset) {
|
||||
i16 pxl8_unpack_i16_be(const u8* buf, usize offset) {
|
||||
return (i16)pxl8_unpack_u16_be(buf, offset);
|
||||
}
|
||||
|
||||
i32 pxl8_unpack_i32_le(const u8* buf, size_t offset) {
|
||||
i32 pxl8_unpack_i32_le(const u8* buf, usize offset) {
|
||||
return (i32)pxl8_unpack_u32_le(buf, offset);
|
||||
}
|
||||
|
||||
i32 pxl8_unpack_i32_be(const u8* buf, size_t offset) {
|
||||
i32 pxl8_unpack_i32_be(const u8* buf, usize offset) {
|
||||
return (i32)pxl8_unpack_u32_be(buf, offset);
|
||||
}
|
||||
|
||||
i64 pxl8_unpack_i64_le(const u8* buf, size_t offset) {
|
||||
i64 pxl8_unpack_i64_le(const u8* buf, usize offset) {
|
||||
return (i64)pxl8_unpack_u64_le(buf, offset);
|
||||
}
|
||||
|
||||
i64 pxl8_unpack_i64_be(const u8* buf, size_t offset) {
|
||||
i64 pxl8_unpack_i64_be(const u8* buf, usize offset) {
|
||||
return (i64)pxl8_unpack_u64_be(buf, offset);
|
||||
}
|
||||
|
||||
f32 pxl8_unpack_f32_le(const u8* buf, size_t offset) {
|
||||
f32 pxl8_unpack_f32_le(const u8* buf, usize offset) {
|
||||
u32 bits = pxl8_unpack_u32_le(buf, offset);
|
||||
f32 result;
|
||||
memcpy(&result, &bits, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
f32 pxl8_unpack_f32_be(const u8* buf, size_t offset) {
|
||||
f32 pxl8_unpack_f32_be(const u8* buf, usize offset) {
|
||||
u32 bits = pxl8_unpack_u32_be(buf, offset);
|
||||
f32 result;
|
||||
memcpy(&result, &bits, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
f64 pxl8_unpack_f64_le(const u8* buf, size_t offset) {
|
||||
f64 pxl8_unpack_f64_le(const u8* buf, usize offset) {
|
||||
u64 bits = pxl8_unpack_u64_le(buf, offset);
|
||||
f64 result;
|
||||
memcpy(&result, &bits, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
f64 pxl8_unpack_f64_be(const u8* buf, size_t offset) {
|
||||
f64 pxl8_unpack_f64_be(const u8* buf, usize offset) {
|
||||
u64 bits = pxl8_unpack_u64_be(buf, offset);
|
||||
f64 result;
|
||||
memcpy(&result, &bits, sizeof(result));
|
||||
|
|
|
|||
|
|
@ -10,43 +10,43 @@ void pxl8_bit_set(u32* val, u8 bit);
|
|||
bool pxl8_bit_test(u32 val, u8 bit);
|
||||
void pxl8_bit_toggle(u32* val, u8 bit);
|
||||
|
||||
void pxl8_pack_u8(u8* buf, size_t offset, u8 val);
|
||||
void pxl8_pack_u16_be(u8* buf, size_t offset, u16 val);
|
||||
void pxl8_pack_u16_le(u8* buf, size_t offset, u16 val);
|
||||
void pxl8_pack_u32_be(u8* buf, size_t offset, u32 val);
|
||||
void pxl8_pack_u32_le(u8* buf, size_t offset, u32 val);
|
||||
void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val);
|
||||
void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val);
|
||||
void pxl8_pack_i8(u8* buf, size_t offset, i8 val);
|
||||
void pxl8_pack_i16_be(u8* buf, size_t offset, i16 val);
|
||||
void pxl8_pack_i16_le(u8* buf, size_t offset, i16 val);
|
||||
void pxl8_pack_i32_be(u8* buf, size_t offset, i32 val);
|
||||
void pxl8_pack_i32_le(u8* buf, size_t offset, i32 val);
|
||||
void pxl8_pack_i64_be(u8* buf, size_t offset, i64 val);
|
||||
void pxl8_pack_i64_le(u8* buf, size_t offset, i64 val);
|
||||
void pxl8_pack_f32_be(u8* buf, size_t offset, f32 val);
|
||||
void pxl8_pack_f32_le(u8* buf, size_t offset, f32 val);
|
||||
void pxl8_pack_f64_be(u8* buf, size_t offset, f64 val);
|
||||
void pxl8_pack_f64_le(u8* buf, size_t offset, f64 val);
|
||||
void pxl8_pack_u8(u8* buf, usize offset, u8 val);
|
||||
void pxl8_pack_u16_be(u8* buf, usize offset, u16 val);
|
||||
void pxl8_pack_u16_le(u8* buf, usize offset, u16 val);
|
||||
void pxl8_pack_u32_be(u8* buf, usize offset, u32 val);
|
||||
void pxl8_pack_u32_le(u8* buf, usize offset, u32 val);
|
||||
void pxl8_pack_u64_be(u8* buf, usize offset, u64 val);
|
||||
void pxl8_pack_u64_le(u8* buf, usize offset, u64 val);
|
||||
void pxl8_pack_i8(u8* buf, usize offset, i8 val);
|
||||
void pxl8_pack_i16_be(u8* buf, usize offset, i16 val);
|
||||
void pxl8_pack_i16_le(u8* buf, usize offset, i16 val);
|
||||
void pxl8_pack_i32_be(u8* buf, usize offset, i32 val);
|
||||
void pxl8_pack_i32_le(u8* buf, usize offset, i32 val);
|
||||
void pxl8_pack_i64_be(u8* buf, usize offset, i64 val);
|
||||
void pxl8_pack_i64_le(u8* buf, usize offset, i64 val);
|
||||
void pxl8_pack_f32_be(u8* buf, usize offset, f32 val);
|
||||
void pxl8_pack_f32_le(u8* buf, usize offset, f32 val);
|
||||
void pxl8_pack_f64_be(u8* buf, usize offset, f64 val);
|
||||
void pxl8_pack_f64_le(u8* buf, usize offset, f64 val);
|
||||
|
||||
u8 pxl8_unpack_u8(const u8* buf, size_t offset);
|
||||
u16 pxl8_unpack_u16_be(const u8* buf, size_t offset);
|
||||
u16 pxl8_unpack_u16_le(const u8* buf, size_t offset);
|
||||
u32 pxl8_unpack_u32_be(const u8* buf, size_t offset);
|
||||
u32 pxl8_unpack_u32_le(const u8* buf, size_t offset);
|
||||
u64 pxl8_unpack_u64_be(const u8* buf, size_t offset);
|
||||
u64 pxl8_unpack_u64_le(const u8* buf, size_t offset);
|
||||
i8 pxl8_unpack_i8(const u8* buf, size_t offset);
|
||||
i16 pxl8_unpack_i16_be(const u8* buf, size_t offset);
|
||||
i16 pxl8_unpack_i16_le(const u8* buf, size_t offset);
|
||||
i32 pxl8_unpack_i32_be(const u8* buf, size_t offset);
|
||||
i32 pxl8_unpack_i32_le(const u8* buf, size_t offset);
|
||||
i64 pxl8_unpack_i64_be(const u8* buf, size_t offset);
|
||||
i64 pxl8_unpack_i64_le(const u8* buf, size_t offset);
|
||||
f32 pxl8_unpack_f32_be(const u8* buf, size_t offset);
|
||||
f32 pxl8_unpack_f32_le(const u8* buf, size_t offset);
|
||||
f64 pxl8_unpack_f64_be(const u8* buf, size_t offset);
|
||||
f64 pxl8_unpack_f64_le(const u8* buf, size_t offset);
|
||||
u8 pxl8_unpack_u8(const u8* buf, usize offset);
|
||||
u16 pxl8_unpack_u16_be(const u8* buf, usize offset);
|
||||
u16 pxl8_unpack_u16_le(const u8* buf, usize offset);
|
||||
u32 pxl8_unpack_u32_be(const u8* buf, usize offset);
|
||||
u32 pxl8_unpack_u32_le(const u8* buf, usize offset);
|
||||
u64 pxl8_unpack_u64_be(const u8* buf, usize offset);
|
||||
u64 pxl8_unpack_u64_le(const u8* buf, usize offset);
|
||||
i8 pxl8_unpack_i8(const u8* buf, usize offset);
|
||||
i16 pxl8_unpack_i16_be(const u8* buf, usize offset);
|
||||
i16 pxl8_unpack_i16_le(const u8* buf, usize offset);
|
||||
i32 pxl8_unpack_i32_be(const u8* buf, usize offset);
|
||||
i32 pxl8_unpack_i32_le(const u8* buf, usize offset);
|
||||
i64 pxl8_unpack_i64_be(const u8* buf, usize offset);
|
||||
i64 pxl8_unpack_i64_le(const u8* buf, usize offset);
|
||||
f32 pxl8_unpack_f32_be(const u8* buf, usize offset);
|
||||
f32 pxl8_unpack_f32_le(const u8* buf, usize offset);
|
||||
f64 pxl8_unpack_f64_be(const u8* buf, usize offset);
|
||||
f64 pxl8_unpack_f64_le(const u8* buf, usize offset);
|
||||
|
||||
typedef struct {
|
||||
const u8* bytes;
|
||||
|
|
|
|||
37
src/core/pxl8_game.h
Normal file
37
src/core/pxl8_game.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_rng.h"
|
||||
#include "pxl8_script.h"
|
||||
#include "pxl8_sfx.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_replay pxl8_replay;
|
||||
|
||||
typedef struct pxl8_game {
|
||||
pxl8_gfx* gfx;
|
||||
pxl8_script* script;
|
||||
pxl8_sfx_mixer* mixer;
|
||||
|
||||
pxl8_rng rng;
|
||||
i32 frame_count;
|
||||
u64 last_time;
|
||||
f32 time;
|
||||
|
||||
f32 fps_accumulator;
|
||||
i32 fps_frame_count;
|
||||
f32 fps;
|
||||
|
||||
pxl8_input_state input;
|
||||
pxl8_input_state prev_input;
|
||||
|
||||
#ifndef NDEBUG
|
||||
pxl8_replay* debug_replay;
|
||||
#endif
|
||||
|
||||
bool repl_mode;
|
||||
bool repl_started;
|
||||
bool running;
|
||||
bool script_loaded;
|
||||
char script_path[256];
|
||||
} pxl8_game;
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#include "pxl8_io.h"
|
||||
#include "pxl8_mem.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
|
@ -10,7 +11,7 @@ static inline char pxl8_to_lower(char c) {
|
|||
return (c >= 'A' && c <= 'Z') ? c + 32 : c;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) {
|
||||
pxl8_result pxl8_io_read_file(const char* path, char** content, usize* size) {
|
||||
if (!path || !content || !size) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
pxl8_cart* cart = pxl8_get_cart();
|
||||
|
|
@ -19,7 +20,7 @@ pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) {
|
|||
u32 cart_size = 0;
|
||||
pxl8_result result = pxl8_cart_read_file(cart, path, &data, &cart_size);
|
||||
if (result == PXL8_OK) {
|
||||
*content = realloc(data, cart_size + 1);
|
||||
*content = pxl8_realloc(data, cart_size + 1);
|
||||
if (!*content) {
|
||||
pxl8_cart_free_file(data);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
|
|
@ -44,13 +45,13 @@ pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) {
|
|||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
*content = malloc(file_size + 1);
|
||||
*content = pxl8_malloc(file_size + 1);
|
||||
if (!*content) {
|
||||
fclose(file);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
size_t bytes_read = fread(*content, 1, file_size, file);
|
||||
usize bytes_read = fread(*content, 1, file_size, file);
|
||||
(*content)[bytes_read] = '\0';
|
||||
*size = bytes_read;
|
||||
|
||||
|
|
@ -58,7 +59,7 @@ pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) {
|
|||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t size) {
|
||||
pxl8_result pxl8_io_write_file(const char* path, const char* content, usize size) {
|
||||
if (!path || !content) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
FILE* file = fopen(path, "wb");
|
||||
|
|
@ -66,17 +67,17 @@ pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t siz
|
|||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
size_t bytes_written = fwrite(content, 1, size, file);
|
||||
usize bytes_written = fwrite(content, 1, size, file);
|
||||
fclose(file);
|
||||
|
||||
return (bytes_written == size) ? PXL8_OK : PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, size_t* size) {
|
||||
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, usize* size) {
|
||||
return pxl8_io_read_file(path, (char**)data, size);
|
||||
}
|
||||
|
||||
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, size_t size) {
|
||||
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, usize size) {
|
||||
return pxl8_io_write_file(path, (const char*)data, size);
|
||||
}
|
||||
|
||||
|
|
@ -112,13 +113,13 @@ pxl8_result pxl8_io_create_directory(const char* path) {
|
|||
|
||||
void pxl8_io_free_file_content(char* content) {
|
||||
if (content) {
|
||||
free(content);
|
||||
pxl8_free(content);
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_io_free_binary_data(u8* data) {
|
||||
if (data) {
|
||||
free(data);
|
||||
pxl8_free(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -144,7 +145,7 @@ static i32 pxl8_key_code(const char* key_name) {
|
|||
};
|
||||
|
||||
char lower_name[64];
|
||||
size_t i;
|
||||
usize i;
|
||||
for (i = 0; i < sizeof(lower_name) - 1 && key_name[i]; i++) {
|
||||
lower_name[i] = pxl8_to_lower(key_name[i]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ bool pxl8_io_file_exists(const char* path);
|
|||
void pxl8_io_free_binary_data(u8* data);
|
||||
void pxl8_io_free_file_content(char* content);
|
||||
f64 pxl8_io_get_file_modified_time(const char* path);
|
||||
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, size_t* size);
|
||||
pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size);
|
||||
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, size_t size);
|
||||
pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t size);
|
||||
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, usize* size);
|
||||
pxl8_result pxl8_io_read_file(const char* path, char** content, usize* size);
|
||||
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, usize size);
|
||||
pxl8_result pxl8_io_write_file(const char* path, const char* content, usize size);
|
||||
|
||||
bool pxl8_key_down(const pxl8_input_state* input, const char* key_name);
|
||||
bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name);
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ void pxl8_log_set_level(pxl8_log_level level) {
|
|||
if (g_log) g_log->level = level;
|
||||
}
|
||||
|
||||
static void log_timestamp(char* buffer, size_t size) {
|
||||
static void log_timestamp(char* buffer, usize size) {
|
||||
time_t now = time(NULL);
|
||||
struct tm* tm_info = localtime(&now);
|
||||
strftime(buffer, size, "%H:%M:%S", tm_info);
|
||||
|
|
|
|||
620
src/core/pxl8_replay.c
Normal file
620
src/core/pxl8_replay.c
Normal file
|
|
@ -0,0 +1,620 @@
|
|||
#include "pxl8_replay.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_mem.h"
|
||||
#include "pxl8_sys.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct pxl8_replay_chunk {
|
||||
u8 type;
|
||||
u32 size;
|
||||
u8* data;
|
||||
struct pxl8_replay_chunk* next;
|
||||
} pxl8_replay_chunk;
|
||||
|
||||
typedef struct pxl8_keyframe_entry {
|
||||
pxl8_keyframe keyframe;
|
||||
pxl8_replay_chunk* input_deltas;
|
||||
struct pxl8_keyframe_entry* next;
|
||||
struct pxl8_keyframe_entry* prev;
|
||||
} pxl8_keyframe_entry;
|
||||
|
||||
struct pxl8_replay {
|
||||
FILE* file;
|
||||
pxl8_replay_header header;
|
||||
bool recording;
|
||||
bool playing;
|
||||
u32 current_frame;
|
||||
|
||||
pxl8_keyframe_entry* keyframes;
|
||||
pxl8_keyframe_entry* current_keyframe;
|
||||
u32 keyframe_count;
|
||||
u32 max_keyframes;
|
||||
|
||||
pxl8_replay_chunk* pending_inputs;
|
||||
pxl8_replay_chunk* pending_inputs_tail;
|
||||
|
||||
pxl8_replay_chunk* audio_events;
|
||||
pxl8_replay_chunk* audio_events_tail;
|
||||
};
|
||||
|
||||
static void pxl8_replay_chunk_free(pxl8_replay_chunk* chunk) {
|
||||
while (chunk) {
|
||||
pxl8_replay_chunk* next = chunk->next;
|
||||
pxl8_free(chunk->data);
|
||||
pxl8_free(chunk);
|
||||
chunk = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void pxl8_replay_keyframe_entry_free(pxl8_keyframe_entry* entry) {
|
||||
while (entry) {
|
||||
pxl8_keyframe_entry* next = entry->next;
|
||||
pxl8_replay_chunk_free(entry->input_deltas);
|
||||
pxl8_free(entry);
|
||||
entry = next;
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_replay* pxl8_replay_create(const char* path, u32 keyframe_interval) {
|
||||
FILE* f = fopen(path, "wb");
|
||||
if (!f) {
|
||||
pxl8_error("Failed to create replay file: %s", path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_replay* r = pxl8_calloc(1, sizeof(pxl8_replay));
|
||||
if (!r) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
r->file = f;
|
||||
r->recording = true;
|
||||
r->playing = false;
|
||||
r->header.magic = PXL8_REPLAY_MAGIC;
|
||||
r->header.version = PXL8_REPLAY_VERSION;
|
||||
r->header.keyframe_interval = keyframe_interval;
|
||||
|
||||
fwrite(&r->header, sizeof(pxl8_replay_header), 1, f);
|
||||
fflush(f);
|
||||
|
||||
pxl8_info("Created replay file: %s (keyframe interval: %u)", path, keyframe_interval);
|
||||
return r;
|
||||
}
|
||||
|
||||
pxl8_replay* pxl8_replay_create_buffer(u32 keyframe_interval, u32 max_keyframes) {
|
||||
pxl8_replay* r = pxl8_calloc(1, sizeof(pxl8_replay));
|
||||
if (!r) return NULL;
|
||||
|
||||
r->recording = true;
|
||||
r->playing = false;
|
||||
r->max_keyframes = max_keyframes;
|
||||
r->header.magic = PXL8_REPLAY_MAGIC;
|
||||
r->header.version = PXL8_REPLAY_VERSION;
|
||||
r->header.keyframe_interval = keyframe_interval;
|
||||
|
||||
pxl8_debug("Created replay buffer (keyframe interval: %u, max: %u)", keyframe_interval, max_keyframes);
|
||||
return r;
|
||||
}
|
||||
|
||||
pxl8_replay* pxl8_replay_open(const char* path) {
|
||||
FILE* f = fopen(path, "rb");
|
||||
if (!f) {
|
||||
pxl8_error("Failed to open replay file: %s", path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_replay_header header;
|
||||
if (fread(&header, sizeof(header), 1, f) != 1) {
|
||||
pxl8_error("Failed to read replay header");
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (header.magic != PXL8_REPLAY_MAGIC) {
|
||||
pxl8_error("Invalid replay magic: 0x%08X (expected 0x%08X)", header.magic, PXL8_REPLAY_MAGIC);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (header.version > PXL8_REPLAY_VERSION) {
|
||||
pxl8_error("Unsupported replay version: %u (max supported: %u)", header.version, PXL8_REPLAY_VERSION);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_replay* r = pxl8_calloc(1, sizeof(pxl8_replay));
|
||||
if (!r) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
r->file = f;
|
||||
r->header = header;
|
||||
r->recording = false;
|
||||
r->playing = true;
|
||||
|
||||
while (!feof(f)) {
|
||||
u8 chunk_type;
|
||||
if (fread(&chunk_type, 1, 1, f) != 1) break;
|
||||
|
||||
if (chunk_type == PXL8_REPLAY_CHUNK_END) break;
|
||||
|
||||
u8 size_bytes[3];
|
||||
if (fread(size_bytes, 3, 1, f) != 1) break;
|
||||
u32 size = size_bytes[0] | (size_bytes[1] << 8) | (size_bytes[2] << 16);
|
||||
|
||||
u8* data = pxl8_malloc(size);
|
||||
if (!data || fread(data, size, 1, f) != 1) {
|
||||
pxl8_free(data);
|
||||
break;
|
||||
}
|
||||
|
||||
if (chunk_type == PXL8_REPLAY_CHUNK_KEYFRAME) {
|
||||
pxl8_keyframe_entry* entry = pxl8_calloc(1, sizeof(pxl8_keyframe_entry));
|
||||
if (entry && size >= sizeof(pxl8_keyframe)) {
|
||||
memcpy(&entry->keyframe, data, sizeof(pxl8_keyframe));
|
||||
entry->prev = r->current_keyframe;
|
||||
if (r->current_keyframe) {
|
||||
r->current_keyframe->next = entry;
|
||||
}
|
||||
r->current_keyframe = entry;
|
||||
if (!r->keyframes) {
|
||||
r->keyframes = entry;
|
||||
}
|
||||
r->keyframe_count++;
|
||||
}
|
||||
pxl8_free(data);
|
||||
} else if (chunk_type == PXL8_REPLAY_CHUNK_INPUT) {
|
||||
if (r->current_keyframe) {
|
||||
pxl8_replay_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_replay_chunk));
|
||||
if (chunk) {
|
||||
chunk->type = chunk_type;
|
||||
chunk->size = size;
|
||||
chunk->data = data;
|
||||
data = NULL;
|
||||
|
||||
if (!r->current_keyframe->input_deltas) {
|
||||
r->current_keyframe->input_deltas = chunk;
|
||||
} else {
|
||||
pxl8_replay_chunk* tail = r->current_keyframe->input_deltas;
|
||||
while (tail->next) tail = tail->next;
|
||||
tail->next = chunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
pxl8_free(data);
|
||||
} else if (chunk_type == PXL8_REPLAY_CHUNK_AUDIO_EVENT) {
|
||||
pxl8_replay_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_replay_chunk));
|
||||
if (chunk) {
|
||||
chunk->type = chunk_type;
|
||||
chunk->size = size;
|
||||
chunk->data = data;
|
||||
data = NULL;
|
||||
|
||||
if (!r->audio_events) {
|
||||
r->audio_events = chunk;
|
||||
r->audio_events_tail = chunk;
|
||||
} else {
|
||||
r->audio_events_tail->next = chunk;
|
||||
r->audio_events_tail = chunk;
|
||||
}
|
||||
}
|
||||
pxl8_free(data);
|
||||
} else {
|
||||
pxl8_free(data);
|
||||
}
|
||||
}
|
||||
|
||||
r->current_keyframe = r->keyframes;
|
||||
pxl8_info("Opened replay: %u frames, %u keyframes", r->header.total_frames, r->keyframe_count);
|
||||
return r;
|
||||
}
|
||||
|
||||
void pxl8_replay_destroy(pxl8_replay* r) {
|
||||
if (!r) return;
|
||||
|
||||
if (r->file) {
|
||||
if (r->recording) {
|
||||
u8 end_chunk = PXL8_REPLAY_CHUNK_END;
|
||||
fwrite(&end_chunk, 1, 1, r->file);
|
||||
|
||||
fseek(r->file, 0, SEEK_SET);
|
||||
fwrite(&r->header, sizeof(pxl8_replay_header), 1, r->file);
|
||||
}
|
||||
fclose(r->file);
|
||||
}
|
||||
|
||||
pxl8_replay_keyframe_entry_free(r->keyframes);
|
||||
pxl8_replay_chunk_free(r->pending_inputs);
|
||||
pxl8_replay_chunk_free(r->audio_events);
|
||||
|
||||
pxl8_free(r);
|
||||
}
|
||||
|
||||
bool pxl8_replay_is_recording(pxl8_replay* r) {
|
||||
return r && r->recording;
|
||||
}
|
||||
|
||||
bool pxl8_replay_is_playing(pxl8_replay* r) {
|
||||
return r && r->playing;
|
||||
}
|
||||
|
||||
u32 pxl8_replay_get_frame(pxl8_replay* r) {
|
||||
return r ? r->current_frame : 0;
|
||||
}
|
||||
|
||||
u32 pxl8_replay_get_total_frames(pxl8_replay* r) {
|
||||
return r ? r->header.total_frames : 0;
|
||||
}
|
||||
|
||||
static void write_chunk(FILE* f, u8 type, const void* data, u32 size) {
|
||||
fwrite(&type, 1, 1, f);
|
||||
u8 size_bytes[3] = {
|
||||
(u8)(size & 0xFF),
|
||||
(u8)((size >> 8) & 0xFF),
|
||||
(u8)((size >> 16) & 0xFF)
|
||||
};
|
||||
fwrite(size_bytes, 3, 1, f);
|
||||
fwrite(data, size, 1, f);
|
||||
}
|
||||
|
||||
static void add_chunk_to_buffer(pxl8_replay* r, u8 type, const void* data, u32 size) {
|
||||
pxl8_replay_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_replay_chunk));
|
||||
if (!chunk) return;
|
||||
|
||||
chunk->type = type;
|
||||
chunk->size = size;
|
||||
chunk->data = pxl8_malloc(size);
|
||||
if (!chunk->data) {
|
||||
pxl8_free(chunk);
|
||||
return;
|
||||
}
|
||||
memcpy(chunk->data, data, size);
|
||||
|
||||
if (!r->pending_inputs) {
|
||||
r->pending_inputs = chunk;
|
||||
r->pending_inputs_tail = chunk;
|
||||
} else {
|
||||
r->pending_inputs_tail->next = chunk;
|
||||
r->pending_inputs_tail = chunk;
|
||||
}
|
||||
}
|
||||
|
||||
static void pack_keys(const bool* keys, u8* packed) {
|
||||
memset(packed, 0, 32);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
if (keys[i]) {
|
||||
packed[i / 8] |= (1 << (i % 8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void unpack_keys(const u8* packed, bool* keys) {
|
||||
for (int i = 0; i < 256; i++) {
|
||||
keys[i] = (packed[i / 8] >> (i % 8)) & 1;
|
||||
}
|
||||
}
|
||||
|
||||
static u8 pack_mouse_buttons(const bool* buttons) {
|
||||
return (buttons[0] ? 1 : 0) | (buttons[1] ? 2 : 0) | (buttons[2] ? 4 : 0);
|
||||
}
|
||||
|
||||
static void unpack_mouse_buttons(u8 packed, bool* buttons) {
|
||||
buttons[0] = (packed & 1) != 0;
|
||||
buttons[1] = (packed & 2) != 0;
|
||||
buttons[2] = (packed & 4) != 0;
|
||||
}
|
||||
|
||||
void pxl8_replay_write_keyframe(pxl8_replay* r, u32 frame, f32 time, pxl8_rng* rng, pxl8_input_state* input) {
|
||||
if (!r || !r->recording) return;
|
||||
|
||||
pxl8_keyframe kf = {0};
|
||||
kf.frame_number = frame;
|
||||
kf.time = time;
|
||||
kf.rng_state = rng ? rng->state : 0;
|
||||
|
||||
if (input) {
|
||||
pack_keys(input->keys_down, kf.keys_down);
|
||||
kf.mouse_buttons = pack_mouse_buttons(input->mouse_buttons_down);
|
||||
kf.mouse_x = (i16)input->mouse_x;
|
||||
kf.mouse_y = (i16)input->mouse_y;
|
||||
}
|
||||
|
||||
if (r->file) {
|
||||
write_chunk(r->file, PXL8_REPLAY_CHUNK_KEYFRAME, &kf, sizeof(kf));
|
||||
fflush(r->file);
|
||||
} else {
|
||||
pxl8_keyframe_entry* entry = pxl8_calloc(1, sizeof(pxl8_keyframe_entry));
|
||||
if (!entry) return;
|
||||
|
||||
entry->keyframe = kf;
|
||||
entry->input_deltas = r->pending_inputs;
|
||||
r->pending_inputs = NULL;
|
||||
r->pending_inputs_tail = NULL;
|
||||
|
||||
if (r->keyframe_count >= r->max_keyframes && r->keyframes) {
|
||||
pxl8_keyframe_entry* oldest = r->keyframes;
|
||||
r->keyframes = oldest->next;
|
||||
if (r->keyframes) {
|
||||
r->keyframes->prev = NULL;
|
||||
}
|
||||
pxl8_replay_chunk_free(oldest->input_deltas);
|
||||
pxl8_free(oldest);
|
||||
r->keyframe_count--;
|
||||
}
|
||||
|
||||
entry->prev = r->current_keyframe;
|
||||
if (r->current_keyframe) {
|
||||
r->current_keyframe->next = entry;
|
||||
}
|
||||
r->current_keyframe = entry;
|
||||
if (!r->keyframes) {
|
||||
r->keyframes = entry;
|
||||
}
|
||||
r->keyframe_count++;
|
||||
}
|
||||
|
||||
r->current_frame = frame;
|
||||
r->header.total_frames = frame;
|
||||
}
|
||||
|
||||
void pxl8_replay_write_input(pxl8_replay* r, u32 frame, pxl8_input_state* prev, pxl8_input_state* curr) {
|
||||
if (!r || !r->recording || !prev || !curr) return;
|
||||
|
||||
u8 buffer[256];
|
||||
u32 offset = 0;
|
||||
|
||||
buffer[offset++] = (u8)(frame & 0xFF);
|
||||
buffer[offset++] = (u8)((frame >> 8) & 0xFF);
|
||||
buffer[offset++] = (u8)((frame >> 16) & 0xFF);
|
||||
buffer[offset++] = (u8)((frame >> 24) & 0xFF);
|
||||
|
||||
u8 key_event_count = 0;
|
||||
u8 key_events[64];
|
||||
u32 key_offset = 0;
|
||||
|
||||
for (int i = 0; i < 256 && key_event_count < 32; i++) {
|
||||
if (prev->keys_down[i] != curr->keys_down[i]) {
|
||||
key_events[key_offset++] = (u8)i;
|
||||
key_events[key_offset++] = curr->keys_down[i] ? 1 : 0;
|
||||
key_event_count++;
|
||||
}
|
||||
}
|
||||
|
||||
buffer[offset++] = key_event_count;
|
||||
memcpy(buffer + offset, key_events, key_offset);
|
||||
offset += key_offset;
|
||||
|
||||
u8 prev_mouse_btns = pack_mouse_buttons(prev->mouse_buttons_down);
|
||||
u8 curr_mouse_btns = pack_mouse_buttons(curr->mouse_buttons_down);
|
||||
|
||||
u8 mouse_flags = 0;
|
||||
if (curr->mouse_x != prev->mouse_x || curr->mouse_y != prev->mouse_y) {
|
||||
mouse_flags |= 0x01;
|
||||
}
|
||||
if (curr_mouse_btns != prev_mouse_btns) {
|
||||
mouse_flags |= 0x02;
|
||||
}
|
||||
if (curr->mouse_wheel_x != 0 || curr->mouse_wheel_y != 0) {
|
||||
mouse_flags |= 0x04;
|
||||
}
|
||||
|
||||
buffer[offset++] = mouse_flags;
|
||||
|
||||
if (mouse_flags & 0x01) {
|
||||
i16 dx = (i16)(curr->mouse_x - prev->mouse_x);
|
||||
i16 dy = (i16)(curr->mouse_y - prev->mouse_y);
|
||||
buffer[offset++] = (u8)(dx & 0xFF);
|
||||
buffer[offset++] = (u8)((dx >> 8) & 0xFF);
|
||||
buffer[offset++] = (u8)(dy & 0xFF);
|
||||
buffer[offset++] = (u8)((dy >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
if (mouse_flags & 0x02) {
|
||||
buffer[offset++] = curr_mouse_btns;
|
||||
}
|
||||
|
||||
if (mouse_flags & 0x04) {
|
||||
buffer[offset++] = (i8)curr->mouse_wheel_x;
|
||||
buffer[offset++] = (i8)curr->mouse_wheel_y;
|
||||
}
|
||||
|
||||
if (r->file) {
|
||||
write_chunk(r->file, PXL8_REPLAY_CHUNK_INPUT, buffer, offset);
|
||||
} else {
|
||||
add_chunk_to_buffer(r, PXL8_REPLAY_CHUNK_INPUT, buffer, offset);
|
||||
}
|
||||
|
||||
r->current_frame = frame;
|
||||
r->header.total_frames = frame;
|
||||
}
|
||||
|
||||
void pxl8_replay_write_audio_event(pxl8_replay* r, u32 frame, u8 event_type, u8 context_id, u8 note, f32 volume) {
|
||||
if (!r || !r->recording) return;
|
||||
|
||||
pxl8_audio_event evt = {
|
||||
.frame_number = frame,
|
||||
.event_type = event_type,
|
||||
.context_id = context_id,
|
||||
.note = note,
|
||||
.volume = volume
|
||||
};
|
||||
|
||||
if (r->file) {
|
||||
write_chunk(r->file, PXL8_REPLAY_CHUNK_AUDIO_EVENT, &evt, sizeof(evt));
|
||||
} else {
|
||||
pxl8_replay_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_replay_chunk));
|
||||
if (!chunk) return;
|
||||
|
||||
chunk->type = PXL8_REPLAY_CHUNK_AUDIO_EVENT;
|
||||
chunk->size = sizeof(evt);
|
||||
chunk->data = pxl8_malloc(sizeof(evt));
|
||||
if (!chunk->data) {
|
||||
pxl8_free(chunk);
|
||||
return;
|
||||
}
|
||||
memcpy(chunk->data, &evt, sizeof(evt));
|
||||
|
||||
if (!r->audio_events) {
|
||||
r->audio_events = chunk;
|
||||
r->audio_events_tail = chunk;
|
||||
} else {
|
||||
r->audio_events_tail->next = chunk;
|
||||
r->audio_events_tail = chunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_replay_close(pxl8_replay* r) {
|
||||
pxl8_replay_destroy(r);
|
||||
}
|
||||
|
||||
bool pxl8_replay_seek_frame(pxl8_replay* r, u32 frame) {
|
||||
if (!r || !r->playing) return false;
|
||||
|
||||
pxl8_keyframe_entry* target = NULL;
|
||||
for (pxl8_keyframe_entry* e = r->keyframes; e; e = e->next) {
|
||||
if (e->keyframe.frame_number <= frame) {
|
||||
target = e;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!target) return false;
|
||||
|
||||
r->current_keyframe = target;
|
||||
r->current_frame = target->keyframe.frame_number;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pxl8_replay_read_frame(pxl8_replay* r, pxl8_input_state* input_out) {
|
||||
if (!r || !r->playing || !r->current_keyframe) return false;
|
||||
|
||||
u32 target_frame = r->current_frame + 1;
|
||||
|
||||
if (target_frame > r->header.total_frames) return false;
|
||||
|
||||
pxl8_replay_chunk* delta = r->current_keyframe->input_deltas;
|
||||
while (delta) {
|
||||
if (delta->size >= 5) {
|
||||
u32 delta_frame = delta->data[0] | (delta->data[1] << 8) | (delta->data[2] << 16) | (delta->data[3] << 24);
|
||||
if (delta_frame == target_frame) {
|
||||
u32 offset = 4;
|
||||
u8 key_event_count = delta->data[offset++];
|
||||
|
||||
for (u8 i = 0; i < key_event_count && offset + 1 < delta->size; i++) {
|
||||
u8 scancode = delta->data[offset++];
|
||||
u8 pressed = delta->data[offset++];
|
||||
input_out->keys_down[scancode] = pressed != 0;
|
||||
}
|
||||
|
||||
if (offset < delta->size) {
|
||||
u8 mouse_flags = delta->data[offset++];
|
||||
|
||||
if (mouse_flags & 0x01 && offset + 3 < delta->size) {
|
||||
i16 dx = (i16)(delta->data[offset] | (delta->data[offset + 1] << 8));
|
||||
i16 dy = (i16)(delta->data[offset + 2] | (delta->data[offset + 3] << 8));
|
||||
offset += 4;
|
||||
input_out->mouse_x += dx;
|
||||
input_out->mouse_y += dy;
|
||||
}
|
||||
|
||||
if (mouse_flags & 0x02 && offset < delta->size) {
|
||||
u8 mouse_btns = delta->data[offset++];
|
||||
unpack_mouse_buttons(mouse_btns, input_out->mouse_buttons_down);
|
||||
}
|
||||
|
||||
if (mouse_flags & 0x04 && offset + 1 < delta->size) {
|
||||
input_out->mouse_wheel_x = (i8)delta->data[offset++];
|
||||
input_out->mouse_wheel_y = (i8)delta->data[offset++];
|
||||
}
|
||||
}
|
||||
|
||||
r->current_frame = target_frame;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
delta = delta->next;
|
||||
}
|
||||
|
||||
if (r->current_keyframe->next && r->current_keyframe->next->keyframe.frame_number <= target_frame) {
|
||||
r->current_keyframe = r->current_keyframe->next;
|
||||
}
|
||||
|
||||
r->current_frame = target_frame;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pxl8_replay_get_keyframe(pxl8_replay* r, u32 frame, pxl8_keyframe* out) {
|
||||
if (!r || !out) return false;
|
||||
|
||||
pxl8_keyframe_entry* target = NULL;
|
||||
for (pxl8_keyframe_entry* e = r->keyframes; e; e = e->next) {
|
||||
if (e->keyframe.frame_number <= frame) {
|
||||
target = e;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!target) return false;
|
||||
|
||||
*out = target->keyframe;
|
||||
return true;
|
||||
}
|
||||
|
||||
void pxl8_replay_apply_keyframe(pxl8_replay* r, pxl8_keyframe* kf, pxl8_rng* rng, pxl8_input_state* input) {
|
||||
if (!kf) return;
|
||||
|
||||
if (rng) {
|
||||
rng->state = kf->rng_state;
|
||||
}
|
||||
|
||||
if (input) {
|
||||
unpack_keys(kf->keys_down, input->keys_down);
|
||||
unpack_mouse_buttons(kf->mouse_buttons, input->mouse_buttons_down);
|
||||
input->mouse_x = kf->mouse_x;
|
||||
input->mouse_y = kf->mouse_y;
|
||||
}
|
||||
|
||||
if (r) {
|
||||
r->current_frame = kf->frame_number;
|
||||
}
|
||||
}
|
||||
|
||||
bool pxl8_replay_export(pxl8_replay* r, const char* path) {
|
||||
if (!r || !r->keyframes) return false;
|
||||
|
||||
FILE* f = fopen(path, "wb");
|
||||
if (!f) {
|
||||
pxl8_error("Failed to create export file: %s", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
pxl8_replay_header header = r->header;
|
||||
fwrite(&header, sizeof(header), 1, f);
|
||||
|
||||
for (pxl8_keyframe_entry* e = r->keyframes; e; e = e->next) {
|
||||
write_chunk(f, PXL8_REPLAY_CHUNK_KEYFRAME, &e->keyframe, sizeof(pxl8_keyframe));
|
||||
|
||||
for (pxl8_replay_chunk* c = e->input_deltas; c; c = c->next) {
|
||||
write_chunk(f, c->type, c->data, c->size);
|
||||
}
|
||||
}
|
||||
|
||||
for (pxl8_replay_chunk* c = r->audio_events; c; c = c->next) {
|
||||
write_chunk(f, c->type, c->data, c->size);
|
||||
}
|
||||
|
||||
u8 end_chunk = PXL8_REPLAY_CHUNK_END;
|
||||
fwrite(&end_chunk, 1, 1, f);
|
||||
|
||||
fclose(f);
|
||||
pxl8_info("Exported replay to: %s (%u frames)", path, header.total_frames);
|
||||
return true;
|
||||
}
|
||||
84
src/core/pxl8_replay.h
Normal file
84
src/core/pxl8_replay.h
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_io.h"
|
||||
#include "pxl8_rng.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#define PXL8_REPLAY_MAGIC 0x31525850
|
||||
#define PXL8_REPLAY_VERSION 1
|
||||
|
||||
#define PXL8_REPLAY_CHUNK_KEYFRAME 0x01
|
||||
#define PXL8_REPLAY_CHUNK_INPUT 0x02
|
||||
#define PXL8_REPLAY_CHUNK_AUDIO_EVENT 0x03
|
||||
#define PXL8_REPLAY_CHUNK_END 0xFF
|
||||
|
||||
#define PXL8_REPLAY_FLAG_HAS_PALETTE (1 << 0)
|
||||
#define PXL8_REPLAY_FLAG_HAS_GLOBALS (1 << 1)
|
||||
|
||||
typedef struct pxl8_replay pxl8_replay;
|
||||
|
||||
typedef struct pxl8_replay_header {
|
||||
u32 magic;
|
||||
u32 version;
|
||||
u32 flags;
|
||||
u32 keyframe_interval;
|
||||
u32 total_frames;
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 reserved;
|
||||
} pxl8_replay_header;
|
||||
|
||||
typedef struct pxl8_keyframe {
|
||||
u32 frame_number;
|
||||
f32 time;
|
||||
u32 rng_state;
|
||||
u8 keys_down[32];
|
||||
u8 mouse_buttons;
|
||||
i16 mouse_x;
|
||||
i16 mouse_y;
|
||||
u8 flags;
|
||||
} pxl8_keyframe;
|
||||
|
||||
typedef struct pxl8_input_delta {
|
||||
u32 frame_number;
|
||||
u8 key_event_count;
|
||||
u8 mouse_flags;
|
||||
} pxl8_input_delta;
|
||||
|
||||
typedef struct pxl8_audio_event {
|
||||
u32 frame_number;
|
||||
u8 event_type;
|
||||
u8 context_id;
|
||||
u8 note;
|
||||
f32 volume;
|
||||
} pxl8_audio_event;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_replay* pxl8_replay_create(const char* path, u32 keyframe_interval);
|
||||
pxl8_replay* pxl8_replay_create_buffer(u32 keyframe_interval, u32 max_keyframes);
|
||||
pxl8_replay* pxl8_replay_open(const char* path);
|
||||
void pxl8_replay_destroy(pxl8_replay* r);
|
||||
|
||||
bool pxl8_replay_is_recording(pxl8_replay* r);
|
||||
bool pxl8_replay_is_playing(pxl8_replay* r);
|
||||
u32 pxl8_replay_get_frame(pxl8_replay* r);
|
||||
u32 pxl8_replay_get_total_frames(pxl8_replay* r);
|
||||
|
||||
void pxl8_replay_write_keyframe(pxl8_replay* r, u32 frame, f32 time, pxl8_rng* rng, pxl8_input_state* input);
|
||||
void pxl8_replay_write_input(pxl8_replay* r, u32 frame, pxl8_input_state* prev, pxl8_input_state* curr);
|
||||
void pxl8_replay_write_audio_event(pxl8_replay* r, u32 frame, u8 event_type, u8 context_id, u8 note, f32 volume);
|
||||
void pxl8_replay_close(pxl8_replay* r);
|
||||
|
||||
bool pxl8_replay_seek_frame(pxl8_replay* r, u32 frame);
|
||||
bool pxl8_replay_read_frame(pxl8_replay* r, pxl8_input_state* input_out);
|
||||
bool pxl8_replay_get_keyframe(pxl8_replay* r, u32 frame, pxl8_keyframe* out);
|
||||
void pxl8_replay_apply_keyframe(pxl8_replay* r, pxl8_keyframe* kf, pxl8_rng* rng, pxl8_input_state* input);
|
||||
|
||||
bool pxl8_replay_export(pxl8_replay* r, const char* path);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
@ -23,6 +23,9 @@ typedef uint16_t u16;
|
|||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
typedef size_t usize;
|
||||
typedef ptrdiff_t isize;
|
||||
|
||||
#if defined(__SIZEOF_INT128__)
|
||||
typedef __int128_t i128;
|
||||
typedef __uint128_t u128;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue