From 2fddd84946a3e63f7e296999b887b3346ba9d1e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Madar=C3=A1sz?= Date: Sat, 21 Oct 2023 11:18:13 +0200 Subject: [PATCH] sync fwk --- _mirror | 2 +- bind/v4k.lua | 189 +++- depot | 2 +- engine/joint/v4k.h | 1988 +++++++++++++++++++++++++++++++++--- engine/split/3rd_eval.h | 490 +++++++++ engine/split/3rd_nuklear.h | 9 +- engine/split/v4k.x.inl | 2 + engine/split/v4k_config.h | 11 +- engine/split/v4k_ds.h | 4 +- engine/split/v4k_font.c | 6 +- engine/split/v4k_main.c | 3 - engine/split/v4k_math.c | 15 +- engine/split/v4k_math.h | 15 +- engine/split/v4k_obj.c | 829 ++++++++++++++- engine/split/v4k_obj.h | 333 +++++- engine/split/v4k_pack.c | 112 +- engine/split/v4k_pack.h | 77 +- engine/split/v4k_reflect.c | 4 +- engine/split/v4k_reflect.h | 6 +- engine/split/v4k_render.c | 6 +- engine/split/v4k_render.h | 2 +- engine/split/v4k_string.h | 8 +- engine/split/v4k_system.c | 33 +- engine/split/v4k_system.h | 2 +- engine/split/v4k_time.h | 2 +- engine/split/v4k_ui.c | 15 +- engine/split/v4k_window.c | 3 +- engine/v4k | 502 ++++++++- engine/v4k.c | 1026 +++++++++++++++++-- engine/v4k.h | 460 +++++++-- 30 files changed, 5628 insertions(+), 528 deletions(-) create mode 100644 engine/split/3rd_eval.h diff --git a/_mirror b/_mirror index e838bef..f7cf64e 160000 --- a/_mirror +++ b/_mirror @@ -1 +1 @@ -Subproject commit e838bef9bea402b1fa3ecb66b74addcb4256b687 +Subproject commit f7cf64ed703b21981c5c5e600bd55c43e0b9e66e diff --git a/bind/v4k.lua b/bind/v4k.lua index 92fc1b9..530ef50 100644 --- a/bind/v4k.lua +++ b/bind/v4k.lua @@ -1143,6 +1143,15 @@ ffi.cdef([[ //lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 font_highlight(const char *text, const void *colors); //lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 font_highlight(const char *text, const void *colors); //lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 font_highlight(const char *text, const void *colors); +//lcpp INF [0000] vec2: macro name but used as C declaration in:API char* ftoa2(vec2 v); +//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC char* ftoa2(vec2 v); +//lcpp INF [0000] vec2: macro name but used as C declaration in: char* ftoa2(vec2 v); +//lcpp INF [0000] vec3: macro name but used as C declaration in:API char* ftoa3(vec3 v); +//lcpp INF [0000] vec3: macro name but used as C declaration in:STATIC char* ftoa3(vec3 v); +//lcpp INF [0000] vec3: macro name but used as C declaration in: char* ftoa3(vec3 v); +//lcpp INF [0000] vec4: macro name but used as C declaration in:API char* ftoa4(vec4 v); +//lcpp INF [0000] vec4: macro name but used as C declaration in:STATIC char* ftoa4(vec4 v); +//lcpp INF [0000] vec4: macro name but used as C declaration in: char* ftoa4(vec4 v); //lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 atof2(const char *s); //lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 atof2(const char *s); //lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 atof2(const char *s); @@ -1152,15 +1161,18 @@ ffi.cdef([[ //lcpp INF [0000] vec4: macro name but used as C declaration in:API vec4 atof4(const char *s); //lcpp INF [0000] vec4: macro name but used as C declaration in:STATIC vec4 atof4(const char *s); //lcpp INF [0000] vec4: macro name but used as C declaration in: vec4 atof4(const char *s); -//lcpp INF [0000] vec2: macro name but used as C declaration in:API char* ftoa2(vec2 v); -//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC char* ftoa2(vec2 v); -//lcpp INF [0000] vec2: macro name but used as C declaration in: char* ftoa2(vec2 v); -//lcpp INF [0000] vec3: macro name but used as C declaration in:API char* ftoa3(vec3 v); -//lcpp INF [0000] vec3: macro name but used as C declaration in:STATIC char* ftoa3(vec3 v); -//lcpp INF [0000] vec3: macro name but used as C declaration in: char* ftoa3(vec3 v); -//lcpp INF [0000] vec4: macro name but used as C declaration in:API char* ftoa4(vec4 v); -//lcpp INF [0000] vec4: macro name but used as C declaration in:STATIC char* ftoa4(vec4 v); -//lcpp INF [0000] vec4: macro name but used as C declaration in: char* ftoa4(vec4 v); +//lcpp INF [0000] vec2i: macro name but used as C declaration in:API char* itoa2(vec2i v); +//lcpp INF [0000] vec2i: macro name but used as C declaration in:STATIC char* itoa2(vec2i v); +//lcpp INF [0000] vec2i: macro name but used as C declaration in: char* itoa2(vec2i v); +//lcpp INF [0000] vec3i: macro name but used as C declaration in:API char* itoa3(vec3i v); +//lcpp INF [0000] vec3i: macro name but used as C declaration in:STATIC char* itoa3(vec3i v); +//lcpp INF [0000] vec3i: macro name but used as C declaration in: char* itoa3(vec3i v); +//lcpp INF [0000] vec2i: macro name but used as C declaration in:API vec2i atoi2(const char *s); +//lcpp INF [0000] vec2i: macro name but used as C declaration in:STATIC vec2i atoi2(const char *s); +//lcpp INF [0000] vec2i: macro name but used as C declaration in: vec2i atoi2(const char *s); +//lcpp INF [0000] vec3i: macro name but used as C declaration in:API vec3i atoi3(const char *s); +//lcpp INF [0000] vec3i: macro name but used as C declaration in:STATIC vec3i atoi3(const char *s); +//lcpp INF [0000] vec3i: macro name but used as C declaration in: vec3i atoi3(const char *s); //lcpp INF [0000] vec2: macro name but used as C declaration in:API void swapf2(vec2 *a, vec2 *b); //lcpp INF [0000] vec2: macro name but used as C declaration in:API void swapf2(vec2 *a, vec2 *b); //lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC void swapf2(vec2 *a, vec2 *b); @@ -1641,9 +1653,10 @@ EASE_IN, EASE_INOUT = EASE_IN * 2, EASE_OUT = 0, }; - float ease(float t01, unsigned mode); - float ease_ping_pong(float t, float(*fn1)(float), float(*fn2)(float)); - float ease_pong_ping(float t, float(*fn1)(float), float(*fn2)(float)); + float ease(float t01, unsigned fn); + float ease_pong(float t01, unsigned fn); + float ease_ping_pong(float t, unsigned fn1, unsigned fn2); + float ease_pong_ping(float t, unsigned fn1, unsigned fn2); float deg (float radians); float rad (float degrees); int mini (int a, int b); @@ -2190,6 +2203,23 @@ uintptr_t id_make(void *ptr); void * id_handle(uintptr_t id); void id_dispose(uintptr_t id); bool id_valid(uintptr_t id); + int semver( int major, int minor, int patch ); + int semvercmp( int v1, int v2 ); +typedef struct byte2 { uint8_t x,y; } byte2; +typedef struct byte3 { uint8_t x,y,z; } byte3; +typedef struct byte4 { uint8_t x,y,z,w; } byte4; +typedef struct int2 { int x,y; } int2; +typedef struct int3 { int x,y,z; } int3; +typedef struct int4 { int x,y,z,w; } int4; +typedef struct uint2 { unsigned int x,y; } uint2; +typedef struct uint3 { unsigned int x,y,z; } uint3; +typedef struct uint4 { unsigned int x,y,z,w; } uint4; +typedef struct float2 { float x,y; } float2; +typedef struct float3 { float x,y,z; } float3; +typedef struct float4 { float x,y,z,w; } float4; +typedef struct double2 { double x,y; } double2; +typedef struct double3 { double x,y,z; } double3; +typedef struct double4 { double x,y,z,w; } double4; char *cc4str(unsigned cc); char *cc8str(uint64_t cc); enum { @@ -2197,13 +2227,20 @@ cc__1 = '1', cc__2, cc__3, cc__4, cc__5, cc__6,cc__7, cc__8, cc__9, cc__0, cc___ cc__A = 'A', cc__B, cc__C, cc__D, cc__E, cc__F,cc__G, cc__H, cc__I, cc__J, cc__K,cc__L, cc__M, cc__N, cc__O, cc__P,cc__Q, cc__R, cc__S, cc__T, cc__U,cc__V, cc__W, cc__X, cc__Y, cc__Z, cc__a = 'a', cc__b, cc__c, cc__d, cc__e, cc__f,cc__g, cc__h, cc__i, cc__j, cc__k,cc__l, cc__m, cc__n, cc__o, cc__p,cc__q, cc__r, cc__s, cc__t, cc__u,cc__v, cc__w, cc__x, cc__y, cc__z, }; + char* ftoa1(float v); + char* ftoa2(vec2 v); + char* ftoa3(vec3 v); + char* ftoa4(vec4 v); + float atof1(const char *s); vec2 atof2(const char *s); vec3 atof3(const char *s); vec4 atof4(const char *s); - char* ftoa(float f); - char* ftoa2(vec2 v); - char* ftoa3(vec3 v); - char* ftoa4(vec4 v); + char* itoa1(int v); + char* itoa2(vec2i v); + char* itoa3(vec3i v); + int atoi1(const char *s); + vec2i atoi2(const char *s); + vec3i atoi3(const char *s); int is_big(); int is_little(); uint16_t swap16( uint16_t x ); @@ -2416,23 +2453,95 @@ enum { NETWORK_USERID = 7, NETWORK_COUNT , NETWORK_CAPACITY }; void server_send_bin(int64_t handle, const void *ptr, int len); void server_drop(int64_t handle); int64_t client_join(const char *ip, int port); - int semver( int major, int minor, int patch ); - int semvercmp( int v1, int v2 ); -typedef struct byte2 { uint8_t x,y; } byte2; -typedef struct byte3 { uint8_t x,y,z; } byte3; -typedef struct byte4 { uint8_t x,y,z,w; } byte4; -typedef struct int2 { int x,y; } int2; -typedef struct int3 { int x,y,z; } int3; -typedef struct int4 { int x,y,z,w; } int4; -typedef struct uint2 { unsigned int x,y; } uint2; -typedef struct uint3 { unsigned int x,y,z; } uint3; -typedef struct uint4 { unsigned int x,y,z,w; } uint4; -typedef struct float2 { float x,y; } float2; -typedef struct float3 { float x,y,z; } float3; -typedef struct float4 { float x,y,z,w; } float4; -typedef struct double2 { double x,y; } double2; -typedef struct double3 { double x,y,z; } double3; -typedef struct double4 { double x,y,z,w; } double4; +typedef struct obj { struct { union { uintptr_t objheader; struct { uintptr_t objtype:8; uintptr_t objheap:1; uintptr_t objsizew:7; uintptr_t objrefs:8; uintptr_t objcomps:1; uintptr_t objnameid:16; uintptr_t objid:16+3; uintptr_t objunused:64-8-7-1-1-8-16-16-3; }; }; }; } obj; +typedef struct entity { struct { union { uintptr_t objheader; struct { uintptr_t objtype:8; uintptr_t objheap:1; uintptr_t objsizew:7; uintptr_t objrefs:8; uintptr_t objcomps:1; uintptr_t objnameid:16; uintptr_t objid:16+3; uintptr_t objunused:64-8-7-1-1-8-16-16-3; }; }; union { struct { uintptr_t objenabled:32, objflagged:32; }; uintptr_t cflags; }; void *c[32]; }; } entity; + obj *objtmp; +void* obj_malloc(unsigned sz); +void* obj_free(void *o); + extern void (*obj_ctor[256])(); + extern void (*obj_dtor[256])(); + extern char* (*obj_save[256])(); + extern bool (*obj_load[256])(); + extern int (*obj_test[256])(); + extern int (*obj_init[256])(); + extern int (*obj_quit[256])(); + extern int (*obj_tick[256])(); + extern int (*obj_draw[256])(); + extern int (*obj_lerp[256])(); + extern int (*obj_edit[256])(); + uintptr_t obj_header(const void *o); + uintptr_t obj_id(const void *o); + const char* obj_name(const void *o); + unsigned obj_typeid(const void *o); + const char* obj_type(const void *o); + int obj_sizeof(const void *o); + int obj_size(const void *o); + char* obj_data(void *o); + const char* obj_datac(const void *o); + void* obj_payload(const void *o); + void* obj_zero(void *o); + void* obj_ref(void *oo); + void* obj_unref(void *oo); + obj* obj_detach(void *c); + obj* obj_attach(void *o, void *c); + obj* obj_root(const void *o); + obj* obj_parent(const void *o); + obj***obj_children(const void *o); + obj***obj_siblings(const void *o); + int obj_dumptree(const void *o); + const char* obj_metaset(const void *o, const char *key, const char *value); + const char* obj_metaget(const void *o, const char *key); + void* obj_swap(void *dst, void *src); + void* obj_copy_fast(void *dst, const void *src); + void* obj_copy(void *dst, const void *src); + int obj_comp_fast(const void *a, const void *b); + int obj_comp(const void *a, const void *b); + int obj_lesser(const void *a, const void *b); + int obj_greater(const void *a, const void *b); + int obj_equal(const void *a, const void *b); + uint64_t obj_hash(const void *o); + bool obj_hexdump(const void *oo); + int obj_print(const void *o); + int obj_printf(const void *o, const char *text); + int obj_console(const void *o); + char* obj_saveini(const void *o); + obj* obj_mergeini(void *o, const char *ini); + obj* obj_loadini(void *o, const char *ini); + char* obj_savejson(const void *o); + obj* obj_mergejson(void *o, const char *json); + obj* obj_loadjson(void *o, const char *json); + char* obj_savebin(const void *o); + obj* obj_mergebin(void *o, const char *sav); + obj* obj_loadbin(void *o, const char *sav); + char* obj_savempack(const void *o); + obj* obj_mergempack(void *o, const char *sav); + obj* obj_loadmpack(void *o, const char *sav); + int obj_push(const void *o); + int obj_pop(void *o); + bool obj_addcomponent(void *object, unsigned c, void *ptr); + bool obj_hascomponent(void *object, unsigned c); + void* obj_getcomponent(void *object, unsigned c); + bool obj_delcomponent(void *object, unsigned c); + bool obj_usecomponent(void *object, unsigned c); + bool obj_offcomponent(void *object, unsigned c); + char* entity_save(entity *self); + void* obj_clone(const void *src); + void* obj_merge(void *dst, const void *src); + void* obj_mutate(void **dst, const void *src); + void* obj_make(const char *str); +typedef enum OBJTYPE_BUILTINS { +OBJTYPE_obj = 0, +OBJTYPE_entity = 1, +OBJTYPE_vec2 = 2, +OBJTYPE_vec3 = 3, +OBJTYPE_vec4 = 4, +OBJTYPE_quat = 5, +OBJTYPE_mat33 = 6, +OBJTYPE_mat34 = 7, +OBJTYPE_mat44 = 8, +OBJTYPE_vec2i = 9, +OBJTYPE_vec3i = 10, +} OBJTYPE_BUILTINS; int profiler_enable(bool on); struct profile_t { double stat; int32_t cost, avg; }; typedef struct { map base; struct { pair p; char * key; struct profile_t val; } tmp, *ptr; struct profile_t* tmpval; int (*typed_cmp)(char *, char *); uint64_t (*typed_hash)(char *); } * profiler_t; @@ -2456,7 +2565,7 @@ unsigned bytes; void * function_find(const char *F); reflect_t member_find(const char *T, const char *M); void * member_findptr(void *obj, const char *T, const char *M); - reflect_t* members_find(const char *T); + reflect_t** members_find(const char *T); void type_inscribe(const char *TY,unsigned TYsz,const char *infos); void enum_inscribe(const char *E,unsigned Eval,const char *infos); void struct_inscribe(const char *T,unsigned Tsz,unsigned OBJTYPEid, const char *infos); @@ -2470,7 +2579,7 @@ typedef unsigned handle; unsigned bgra( uint8_t b, uint8_t g, uint8_t r, uint8_t a ); unsigned rgbaf( float r, float g, float b, float a ); unsigned bgraf( float b, float g, float r, float a ); - float alpha( unsigned rgba ); + unsigned alpha( unsigned rgba ); enum IMAGE_FLAGS { IMAGE_R =4096, IMAGE_RG =8192, @@ -3031,14 +3140,14 @@ int u_coefficients_sh; char* strjoin(char** list, const char *separator); char * string8(const wchar_t *str); uint32_t* string32( const char *utf8 ); -unsigned intern( const char *string ); -const char *quark( unsigned key ); + unsigned intern( const char *string ); + const char *quark( unsigned key ); typedef struct quarks_db { char* blob; vec2i* entries; } quarks_db; -unsigned quark_intern( quarks_db*, const char *string ); -const char *quark_string( quarks_db*, unsigned key ); + unsigned quark_intern( quarks_db*, const char *string ); + const char *quark_string( quarks_db*, unsigned key ); uint64_t date(); uint64_t date_epoch(); char* date_string(); @@ -3090,7 +3199,7 @@ typedef vec3i guid; void alert(const char *message); void hexdump( const void *ptr, unsigned len ); void hexdumpf( FILE *fp, const void *ptr, unsigned len, int width ); - void breakpoint(const char *optional_reason); + void breakpoint(); bool has_debugger(); void trap_install(void); const char *trap_name(int signal); diff --git a/depot b/depot index 80c739f..ea89db8 160000 --- a/depot +++ b/depot @@ -1 +1 @@ -Subproject commit 80c739fc0ce8381e2200f347654b23b646f12fba +Subproject commit ea89db8ecd2b32fca2a21f67f486601de5c6a93c diff --git a/engine/joint/v4k.h b/engine/joint/v4k.h index 8c57ad5..5e4ac9c 100644 --- a/engine/joint/v4k.h +++ b/engine/joint/v4k.h @@ -14200,7 +14200,7 @@ extern "C" { #define conc4t(a,b) a##b ///- #define macro(name) concat(name, __LINE__) -#define unique(name) concat(concat(name, L##__LINE__), __COUNTER__) +#define unique(name) concat(concat(concat(name,concat(_L,__LINE__)),_),__COUNTER__) #define defer(begin,end) for(int macro(i) = ((begin), 0); !macro(i); macro(i) = ((end), 1)) #define scope(end) defer((void)0, end) #define benchmark for(double macro(i) = 1, macro(t) = (time_ss(),-time_ss()); macro(i); macro(t)+=time_ss(), macro(i)=0, printf("%.4fs %2.f%% (" FILELINE ")\n", macro(t), macro(t)*100/0.0166667 )) @@ -14225,8 +14225,8 @@ extern "C" { #define ASSERT(expr, ...) (void)0 #define ASSERT_ONCE(expr, ...) (void)0 #else -#define ASSERT(expr, ...) do { int fool_msvc[] = {0,}; if(!(expr)) { fool_msvc[0]++; breakpoint(va("!Expression failed: " #expr " " FILELINE "\n" __VA_ARGS__)); } } while(0) -#define ASSERT_ONCE(expr, ...) do { int fool_msvc[] = {0,}; if(!(expr)) { fool_msvc[0]++; static int seen = 0; if(!seen) seen = 1, breakpoint(va("!Expression failed: " #expr " " FILELINE "\n" __VA_ARGS__)); } } while(0) +#define ASSERT(expr, ...) do { int fool_msvc[] = {0,}; if(!(expr)) { fool_msvc[0]++; alert(va("!Expression failed: " #expr " " FILELINE "\n" __VA_ARGS__)), breakpoint(); } } while(0) +#define ASSERT_ONCE(expr, ...) do { int fool_msvc[] = {0,}; if(!(expr)) { fool_msvc[0]++; static int seen = 0; if(!seen) seen = 1, alert(va("!Expression failed: " #expr " " FILELINE "\n" __VA_ARGS__)), breakpoint(); } } while(0) #endif #define STATIC_ASSERT(EXPR) typedef struct { unsigned macro(static_assert_on_L) : !!(EXPR); } unique(static_assert_on_L) @@ -14305,6 +14305,7 @@ extern "C" { // note: based on code by Joe Lowe (public domain). // note: XIU for C initializers, XCU for C++ initializers, XTU for C deinitializers +#define AUTORUN AUTORUN_( unique(fn) ) #ifdef __cplusplus #define AUTORUN_(fn) \ static void fn(void); \ @@ -14328,8 +14329,6 @@ extern "C" { static void fn(void) #endif -#define AUTORUN AUTORUN_( concat(concat(concat(fn_L,__LINE__),_),__COUNTER__) ) - #if 0 // autorun demo void byebye(void) { puts("seen after main()"); } AUTORUN { puts("seen before main()"); } @@ -14346,7 +14345,7 @@ AUTORUN { puts("seen before main() too"); atexit( byebye ); } // ----------------------------------------------------------------------------- // visibility -// win32 users would need to -DAPI=IMPORT/EXPORT as needed when using/building V4K as DLL. +// win32 users would need to -DAPI=EXPORT/IMPORT as needed when building/using V4K as DLL. #define IMPORT ifdef(win32, ifdef(gcc, __attribute__ ((dllimport)), __declspec(dllimport))) #define EXPORT ifdef(win32, ifdef(gcc, __attribute__ ((dllexport)), __declspec(dllexport))) @@ -14519,7 +14518,7 @@ static __thread unsigned array_n_; #define array_vlen_(t) ( vlen(t) - 0 ) #define array_realloc_(t,n) ( (t) = array_cast(t) vrealloc((t), ((n)+0) * sizeof(0[t])) ) #define array_free(t) array_clear(t) -#else // new: with reserve support (bugs?) +#else // new: with reserve support (@todo: check for bugs?) #define array_reserve(t, n) ( array_realloc_((t),(n)), array_clear(t) ) #define array_clear(t) ( array_realloc_((t),0) ) // -1 #define array_vlen_(t) ( vlen(t) - sizeof(0[t]) ) // -1 @@ -14566,7 +14565,7 @@ static __thread unsigned array_n_; memcpy( (t), src, array_count(src) * sizeof(0[t])); \ } while(0) -#define array_erase_fast(t, i) do { /*may alter ordering*/ \ +#define array_erase_fast(t, i) do { /*alters ordering*/ \ memcpy( &(t)[i], &(t)[array_count(t) - 1], sizeof(0[t])); \ array_pop(t); \ } while(0) @@ -14888,9 +14887,9 @@ API void (map_clear)(map* m); // Credits: @ands+@krig+@vurtun (PD), @datenwolf (WTFPL2), @evanw+@barerose (CC0), @sgorsten (Unlicense). #define C_EPSILON (1e-6) -#define C_PI (3.141592654f) // (3.14159265358979323846f) -#define TO_RAD (C_PI/180.f) -#define TO_DEG (180.f/C_PI) +#define C_PI (3.14159265358979323846f) // (3.141592654f) +#define TO_RAD (C_PI/180) +#define TO_DEG (180/C_PI) // ---------------------------------------------------------------------------- @@ -14922,7 +14921,6 @@ API void randset(uint64_t state); API uint64_t rand64(void); API double randf(void); // [0, 1) interval API int randi(int mini, int maxi); // [mini, maxi) interval -//API double rng(void); // [0..1) Lehmer RNG "minimal standard" // ---------------------------------------------------------------------------- @@ -14988,10 +14986,10 @@ enum EASE_FLAGS { EASE_OUT = 0, }; -API float ease(float t01, unsigned mode); // 0=linear,1=out_sine...31=inout_perlin - -API float ease_ping_pong(float t, float(*fn1)(float), float(*fn2)(float)); -API float ease_pong_ping(float t, float(*fn1)(float), float(*fn2)(float)); +API float ease(float t01, unsigned fn); // / 0-to-1 +API float ease_pong(float t01, unsigned fn); // \ 1-to-0 +API float ease_ping_pong(float t, unsigned fn1, unsigned fn2); // /\ 0-to-1-to-0 +API float ease_pong_ping(float t, unsigned fn1, unsigned fn2); // \/ 1-to-0-to-1 // ---------------------------------------------------------------------------- @@ -15980,6 +15978,64 @@ bool id_valid(uintptr_t id); #line 0 #line 1 "engine/split/v4k_pack.h" +// ----------------------------------------------------------------------------- +// semantic versioning in a single byte (octal) +// - rlyeh, public domain. +// +// - single octal byte that represents semantic versioning (major.minor.patch). +// - allowed range [0000..0377] ( <-> [0..255] decimal ) +// - comparison checks only major.minor tuple as per convention. + +API int semver( int major, int minor, int patch ); +API int semvercmp( int v1, int v2 ); + +#define SEMVER(major,minor,patch) (0100 * (major) + 010 * (minor) + (patch)) +#define SEMVERCMP(v1,v2) (((v1) & 0110) - ((v2) & 0110)) +#define SEMVERFMT "%03o" + +// ----------------------------------------------------------------------------- +// storage types. refer to vec2i/3i, vec2/3/4 if you plan to do math operations + +typedef struct byte2 { uint8_t x,y; } byte2; +typedef struct byte3 { uint8_t x,y,z; } byte3; +typedef struct byte4 { uint8_t x,y,z,w; } byte4; + +typedef struct int2 { int x,y; } int2; +typedef struct int3 { int x,y,z; } int3; +typedef struct int4 { int x,y,z,w; } int4; + +typedef struct uint2 { unsigned int x,y; } uint2; +typedef struct uint3 { unsigned int x,y,z; } uint3; +typedef struct uint4 { unsigned int x,y,z,w; } uint4; + +typedef struct float2 { float x,y; } float2; +typedef struct float3 { float x,y,z; } float3; +typedef struct float4 { float x,y,z,w; } float4; + +typedef struct double2 { double x,y; } double2; +typedef struct double3 { double x,y,z; } double3; +typedef struct double4 { double x,y,z,w; } double4; + +#define byte2(x,y) M_CAST(byte2, (uint8_t)(x), (uint8_t)(y) ) +#define byte3(x,y,z) M_CAST(byte3, (uint8_t)(x), (uint8_t)(y), (uint8_t)(z) ) +#define byte4(x,y,z,w) M_CAST(byte4, (uint8_t)(x), (uint8_t)(y), (uint8_t)(z), (uint8_t)(w) ) + +#define int2(x,y) M_CAST(int2, (int)(x), (int)(y) ) +#define int3(x,y,z) M_CAST(int3, (int)(x), (int)(y), (int)(z) ) +#define int4(x,y,z,w) M_CAST(int4, (int)(x), (int)(y), (int)(z), (int)(w) ) + +#define uint2(x,y) M_CAST(uint2, (unsigned)(x), (unsigned)(y) ) +#define uint3(x,y,z) M_CAST(uint3, (unsigned)(x), (unsigned)(y), (unsigned)(z) ) +#define uint4(x,y,z,w) M_CAST(uint4, (unsigned)(x), (unsigned)(y), (unsigned)(z), (unsigned)(w) ) + +#define float2(x,y) M_CAST(float2, (float)(x), (float)(y) ) +#define float3(x,y,z) M_CAST(float3, (float)(x), (float)(y), (float)(z) ) +#define float4(x,y,z,w) M_CAST(float4, (float)(x), (float)(y), (float)(z), (float)(w) ) + +#define double2(x,y) M_CAST(double2, (double)(x), (double)(y) ) +#define double3(x,y,z) M_CAST(double3, (double)(x), (double)(y), (double)(z) ) +#define double4(x,y,z,w) M_CAST(double4, (double)(x), (double)(y), (double)(z), (double)(w) ) + // ----------------------------------------------------------------------------- // compile-time fourcc, eightcc @@ -16008,16 +16064,25 @@ enum { #define cc7(a,b,c,d,e,f,g) cc8(,a,b,c,d,e,f,g) // ---------------------------------------------------------------------------- -// float conversion (text) +// text conversions +API char* ftoa1(float v); +API char* ftoa2(vec2 v); +API char* ftoa3(vec3 v); +API char* ftoa4(vec4 v); + +API float atof1(const char *s); API vec2 atof2(const char *s); API vec3 atof3(const char *s); API vec4 atof4(const char *s); -API char* ftoa(float f); -API char* ftoa2(vec2 v); -API char* ftoa3(vec3 v); -API char* ftoa4(vec4 v); +API char* itoa1(int v); +API char* itoa2(vec2i v); +API char* itoa3(vec3i v); + +API int atoi1(const char *s); +API vec2i atoi2(const char *s); +API vec3i atoi3(const char *s); // ---------------------------------------------------------------------------- // endianness @@ -16512,63 +16577,312 @@ API int64_t client_join(const char *ip, int port); #line 0 #line 1 "engine/split/v4k_obj.h" -// ----------------------------------------------------------------------------- -// semantic versioning in a single byte (octal) +// C objects framework // - rlyeh, public domain. // -// - single octal byte that represents semantic versioning (major.minor.patch). -// - allowed range [0000..0377] ( <-> [0..255] decimal ) -// - comparison checks only major.minor tuple as per convention. +// ## object limitations +// - 8-byte overhead per object +// - XX-byte overhead per object-entity +// - 32 components max per object-entity +// - 256 classes max per game +// - 256 references max per object +// - 1024K bytes max per object +// - 8 generations + 64K IDs per running instance (19-bit IDs) +// - support for pragma pack(1) structs not enabled by default. -API int semver( int major, int minor, int patch ); -API int semvercmp( int v1, int v2 ); +/* /!\ if you plan to use pragma pack(1) on any struct, you need #define OBJ_MIN_PRAGMAPACK_BITS 0 at the expense of max class size /!\ */ +#ifndef OBJ_MIN_PRAGMAPACK_BITS +//#define OBJ_MIN_PRAGMAPACK_BITS 3 // allows pragma packs >= 8. objsizew becomes 7<<3, so 1024 bytes max per class (default) +#define OBJ_MIN_PRAGMAPACK_BITS 1 // allows pragma packs >= 2. objsizew becomes 7<<1, so 256 bytes max per class +//#define OBJ_MIN_PRAGMAPACK_BITS 0 // allows pragma packs >= 1. objsizew becomes 7<<0, so 128 bytes max per class +#endif -#define SEMVER(major,minor,patch) (0100 * (major) + 010 * (minor) + (patch)) -#define SEMVERCMP(v1,v2) (((v1) & 0110) - ((v2) & 0110)) -#define SEMVERFMT "%03o" +#define OBJHEADER \ + union { \ + uintptr_t objheader; \ + struct { \ + uintptr_t objtype:8; \ + uintptr_t objheap:1; \ + uintptr_t objsizew:7; \ + uintptr_t objrefs:8; \ + uintptr_t objcomps:1; /* << can be removed? check payload ptr instead? */ \ + uintptr_t objnameid:16; \ + uintptr_t objid:ID_INDEX_BITS+ID_COUNT_BITS; /*16+3*/ \ + uintptr_t objunused:64-8-7-1-1-8-16-ID_INDEX_BITS-ID_COUNT_BITS; /*4*/ \ + }; \ + }; -// ----------------------------------------------------------------------------- -// storage types. refer to vec2i/3i, vec2/3/4 if you plan to do math operations +#define OBJ \ + struct { OBJHEADER }; -typedef struct byte2 { uint8_t x,y; } byte2; -typedef struct byte3 { uint8_t x,y,z; } byte3; -typedef struct byte4 { uint8_t x,y,z,w; } byte4; +// ---------------------------------------------------------------------------- +// syntax sugars -typedef struct int2 { int x,y; } int2; -typedef struct int3 { int x,y,z; } int3; -typedef struct int4 { int x,y,z,w; } int4; +#ifdef OBJTYPE +#undef OBJTYPE +#endif -typedef struct uint2 { unsigned int x,y; } uint2; -typedef struct uint3 { unsigned int x,y,z; } uint3; -typedef struct uint4 { unsigned int x,y,z,w; } uint4; +#define OBJTYPE(T) \ + OBJTYPE_##T -typedef struct float2 { float x,y; } float2; -typedef struct float3 { float x,y,z; } float3; -typedef struct float4 { float x,y,z,w; } float4; +#define OBJTYPEDEF(NAME,N) \ + enum { OBJTYPE(NAME) = N }; \ + STATIC_ASSERT( N <= 255 ); \ + STATIC_ASSERT( (sizeof(NAME) & ((1<objheader = HEAP ? id_make(PTR) : 0, /*should assign to .objid instead. however, id_make() returns shifted bits already*/ \ + (PTR)->objnameid = (OBJ_NAMEID), \ + (PTR)->objtype = (OBJ_TYPE), \ + (PTR)->objheap = (HEAP), \ + (PTR)->objsizew = (SIZEOF_OBJ>>OBJ_MIN_PRAGMAPACK_BITS)) +#define OBJ_CTOR_PTR(PTR,HEAP,OBJ_NAMEID,SIZEOF_OBJ,OBJ_TYPE) ( \ + OBJ_CTOR_HDR(PTR,HEAP,OBJ_NAMEID,SIZEOF_OBJ,OBJ_TYPE), \ + obj_ctor(PTR)) +#define OBJ_CTOR(TYPE, NAME, HEAP, PAYLOAD_SIZE, ...) (TYPE*)( \ + objtmp = (HEAP ? MALLOC(sizeof(TYPE)+(PAYLOAD_SIZE)) : ALLOCA(sizeof(TYPE)+(PAYLOAD_SIZE))), \ + *(TYPE*)objtmp = ((TYPE){ {0}, __VA_ARGS__}), \ + ((PAYLOAD_SIZE) ? memset((char*)objtmp + sizeof(TYPE), 0, (PAYLOAD_SIZE)) : objtmp), \ + ( OBJTYPES[ OBJTYPE(TYPE) ] = #TYPE ), \ + OBJ_CTOR_PTR(objtmp, HEAP,intern(NAME),sizeof(TYPE),OBJTYPE(TYPE)), \ + ifdef(debug, (obj_printf)(objtmp, va("%s", callstack(+16))), 0), \ + objtmp) + +#define obj(TYPE, ...) obj_ext(TYPE, #TYPE, __VA_ARGS__) +#define obj_ext(TYPE, NAME, ...) *OBJ_CTOR(TYPE, NAME, 0, sizeof(array(obj*)), __VA_ARGS__) + +#define obj_new(TYPE, ...) obj_new_ext(TYPE, #TYPE, __VA_ARGS__) +#define obj_new_ext(TYPE, NAME, ...) OBJ_CTOR(TYPE, NAME, 1, sizeof(array(obj*)), __VA_ARGS__) + +void* obj_malloc(unsigned sz); +void* obj_free(void *o); + +// ---------------------------------------------------------------------------- +// obj generics. can be extended. + +#define obj_ctor(o,...) obj_method(ctor, o, ##__VA_ARGS__) +#define obj_dtor(o,...) obj_method(dtor, o, ##__VA_ARGS__) + +#define obj_save(o,...) obj_method(save, o, ##__VA_ARGS__) +#define obj_load(o,...) obj_method(load, o, ##__VA_ARGS__) + +#define obj_test(o,...) obj_method(test, o, ##__VA_ARGS__) + +#define obj_init(o,...) obj_method(init, o, ##__VA_ARGS__) +#define obj_quit(o,...) obj_method(quit, o, ##__VA_ARGS__) +#define obj_tick(o,...) obj_method(tick, o, ##__VA_ARGS__) +#define obj_draw(o,...) obj_method(draw, o, ##__VA_ARGS__) + +#define obj_lerp(o,...) obj_method(lerp, o, ##__VA_ARGS__) +#define obj_edit(o,...) obj_method(edit, o, ##__VA_ARGS__) + +// --- syntax sugars + +#define obj_extend(T,func) (obj_##func[OBJTYPE(T)] = (void*)T##_##func) +#define obj_method(method,o,...) (obj_##method[((obj*)(o))->objtype](o,##__VA_ARGS__)) // (obj_##method[((obj*)(o))->objtype]((o), ##__VA_ARGS__)) + +#define obj_vtable(func,RC,...) RC macro(obj_##func)(){ __VA_ARGS__ }; RC (*obj_##func[256])() = { REPEAT256(macro(obj_##func)) }; +#define obj_vtable_null(func,RC) RC (*obj_##func[256])() = { 0 }; // null virtual table. will crash unless obj_extend'ed + +#define REPEAT16(f) f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f +#define REPEAT64(f) REPEAT16(f),REPEAT16(f),REPEAT16(f),REPEAT16(f) +#define REPEAT256(f) REPEAT64(f),REPEAT64(f),REPEAT64(f),REPEAT64(f) + +// --- declare vtables + +API extern void (*obj_ctor[256])(); ///- +API extern void (*obj_dtor[256])(); ///- + +API extern char* (*obj_save[256])(); ///- +API extern bool (*obj_load[256])(); ///- +API extern int (*obj_test[256])(); ///- + +API extern int (*obj_init[256])(); ///- +API extern int (*obj_quit[256])(); ///- +API extern int (*obj_tick[256])(); ///- +API extern int (*obj_draw[256])(); ///- + +API extern int (*obj_lerp[256])(); ///- +API extern int (*obj_edit[256])(); ///- + +// ---------------------------------------------------------------------------- +// core + +API uintptr_t obj_header(const void *o); + +API uintptr_t obj_id(const void *o); +API const char* obj_name(const void *o); + +API unsigned obj_typeid(const void *o); +API const char* obj_type(const void *o); + +API int obj_sizeof(const void *o); +API int obj_size(const void *o); // size of all members together in struct. may include padding bytes. + +API char* obj_data(void *o); // pointer to the first member in struct +API const char* obj_datac(const void *o); // const pointer to the first struct member + +API void* obj_payload(const void *o); // pointer right after last member in struct +API void* obj_zero(void *o); // reset all object members + +// ---------------------------------------------------------------------------- +// refcounting + +API void* obj_ref(void *oo); +API void* obj_unref(void *oo); + +// ---------------------------------------------------------------------------- +// scene tree + +// non-recursive +#define each_objchild(p,t,o) \ + (array(obj*)* children = obj_children(p); children; children = 0) \ + for(int _i = 1, _end = array_count(*children); _i < _end; ++_i) \ + for(t *o = (t *)((*children)[_i]); o && 0[*children]; o = 0) + +API obj* obj_detach(void *c); +API obj* obj_attach(void *o, void *c); + +API obj* obj_root(const void *o); +API obj* obj_parent(const void *o); +API array(obj*)*obj_children(const void *o); +API array(obj*)*obj_siblings(const void *o); + +API int obj_dumptree(const void *o); + +// ---------------------------------------------------------------------------- +// metadata + +API const char* obj_metaset(const void *o, const char *key, const char *value); +API const char* obj_metaget(const void *o, const char *key); + +// ---------------------------------------------------------------------------- +// stl + +API void* obj_swap(void *dst, void *src); +API void* obj_copy_fast(void *dst, const void *src); +API void* obj_copy(void *dst, const void *src); + +API int obj_comp_fast(const void *a, const void *b); +API int obj_comp(const void *a, const void *b); +API int obj_lesser(const void *a, const void *b); +API int obj_greater(const void *a, const void *b); +API int obj_equal(const void *a, const void *b); + +API uint64_t obj_hash(const void *o); + +// ---------------------------------------------------------------------------- +// debug + +API bool obj_hexdump(const void *oo); +API int obj_print(const void *o); + +API int obj_printf(const void *o, const char *text); +API int obj_console(const void *o); // obj_output() ? + +#define obj_printf(o, ...) obj_printf(o, va(__VA_ARGS__)) + +// ---------------------------------------------------------------------------- +// serialization + +API char* obj_saveini(const void *o); +API obj* obj_mergeini(void *o, const char *ini); +API obj* obj_loadini(void *o, const char *ini); + +API char* obj_savejson(const void *o); +API obj* obj_mergejson(void *o, const char *json); +API obj* obj_loadjson(void *o, const char *json); + +API char* obj_savebin(const void *o); +API obj* obj_mergebin(void *o, const char *sav); +API obj* obj_loadbin(void *o, const char *sav); + +API char* obj_savempack(const void *o); // @todo +API obj* obj_mergempack(void *o, const char *sav); // @todo +API obj* obj_loadmpack(void *o, const char *sav); // @todo + +API int obj_push(const void *o); +API int obj_pop(void *o); + +// ---------------------------------------------------------------------------- +// components + +API bool obj_addcomponent(void *object, unsigned c, void *ptr); +API bool obj_hascomponent(void *object, unsigned c); +API void* obj_getcomponent(void *object, unsigned c); +API bool obj_delcomponent(void *object, unsigned c); +API bool obj_usecomponent(void *object, unsigned c); +API bool obj_offcomponent(void *object, unsigned c); + +API char* entity_save(entity *self); + +// ---------------------------------------------------------------------------- +// reflection + +#define each_objmember(oo,TYPE,NAME,PTR) \ + (array(reflect_t) *found_ = members_find(quark(((obj*)oo)->objnameid)); found_; found_ = 0) \ + for(int it_ = 0, end_ = array_count(*found_); it_ != end_; ++it_ ) \ + for(reflect_t *R = &(*found_)[it_]; R; R = 0 ) \ + for(const char *NAME = R->name, *TYPE = R->type; NAME || TYPE; ) \ + for(void *PTR = ((char*)oo) + R->sz ; NAME || TYPE ; NAME = TYPE = 0 ) + +API void* obj_clone(const void *src); +API void* obj_merge(void *dst, const void *src); // @testme +API void* obj_mutate(void **dst, const void *src); +API void* obj_make(const char *str); + +// built-ins + +typedef enum OBJTYPE_BUILTINS { + OBJTYPE_obj = 0, + OBJTYPE_entity = 1, + OBJTYPE_vec2 = 2, + OBJTYPE_vec3 = 3, + OBJTYPE_vec4 = 4, + OBJTYPE_quat = 5, + OBJTYPE_mat33 = 6, + OBJTYPE_mat34 = 7, + OBJTYPE_mat44 = 8, + OBJTYPE_vec2i = 9, + OBJTYPE_vec3i = 10, +} OBJTYPE_BUILTINS; -#define double2(x,y) M_CAST(double2, (double)(x), (double)(y) ) -#define double3(x,y,z) M_CAST(double3, (double)(x), (double)(y), (double)(z) ) -#define double4(x,y,z,w) M_CAST(double4, (double)(x), (double)(y), (double)(z), (double)(w) ) #line 0 #line 1 "engine/split/v4k_profile.h" @@ -16649,14 +16963,14 @@ API void * function_find(const char *F); API reflect_t member_find(const char *T, const char *M); /// find specific member API void * member_findptr(void *obj, const char *T, const char *M); // @deprecate -API array(reflect_t) members_find(const char *T); +API array(reflect_t)* members_find(const char *T); // iterate members in a struct #define each_member(T,R) \ - (array(reflect_t)*found_ = map_find(members, intern(T)); found_; found_ = 0) \ + (array(reflect_t) *found_ = members_find(T); found_; found_ = 0) \ for(int it_ = 0, end_ = array_count(*found_); it_ != end_; ++it_ ) \ - for(reflect_t *R = (*found_)+it_; R; R = 0 ) + for(reflect_t *R = &(*found_)[it_]; R; R = 0 ) // private api, still exposed @@ -16689,7 +17003,7 @@ API unsigned rgba( uint8_t r, uint8_t g, uint8_t b, uint8_t a ); API unsigned bgra( uint8_t b, uint8_t g, uint8_t r, uint8_t a ); API unsigned rgbaf( float r, float g, float b, float a ); API unsigned bgraf( float b, float g, float r, float a ); -API float alpha( unsigned rgba ); +API unsigned alpha( unsigned rgba ); #define RGBX(rgb,x) ( ((rgb)&0xFFFFFF) | (((unsigned)(x))<<24) ) #define RGB3(r,g,b) ( (255<<24) | ((r)<<16) | ((g)<<8) | (b) ) @@ -17675,16 +17989,16 @@ API array(uint32_t) string32( const char *utf8 ); /// convert from utf8 to utf32 // ## string interning (quarks) // - rlyeh, public domain. -unsigned intern( const char *string ); -const char *quark( unsigned key ); +API unsigned intern( const char *string ); +API const char *quark( unsigned key ); typedef struct quarks_db { array(char) blob; array(vec2i) entries; } quarks_db; -unsigned quark_intern( quarks_db*, const char *string ); -const char *quark_string( quarks_db*, unsigned key ); +API unsigned quark_intern( quarks_db*, const char *string ); +API const char *quark_string( quarks_db*, unsigned key ); #line 0 #line 1 "engine/split/v4k_time.h" @@ -17714,7 +18028,7 @@ API void timer_destroy(unsigned timer_handle); // // also similar to a mongo object id, 12 bytes as follows: // - 4-byte timestamp (ss). epoch: Tuesday, 12 September 2023 6:06:56 -// - 2-byte (machine or app hash) +// - 2-byte (machine, hash or app id) // - 2-byte (thread-id) // - 4-byte (rand counter, that gets increased at every id creation) @@ -17786,7 +18100,7 @@ API void die(const char *message); API void alert(const char *message); API void hexdump( const void *ptr, unsigned len ); API void hexdumpf( FILE *fp, const void *ptr, unsigned len, int width ); -API void breakpoint(const char *optional_reason); +API void breakpoint(); API bool has_debugger(); API void trap_install(void); @@ -217096,7 +217410,7 @@ nk_utf_decode(const char *c, nk_rune *u, int clen) *u = NK_UTF_INVALID; udecoded = nk_utf_decode_byte(c[0], &len); - if (!NK_BETWEEN(len, 1, NK_UTF_SIZE)) + if (!NK_BETWEEN(len, 1, NK_UTF_SIZE+1)) //< @r-lyeh: add +1 for len<=4 return 1; for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { @@ -229231,7 +229545,12 @@ window->is_window_resizing |= layout->flags & NK_WINDOW_SCALE_TOP ? NK_WINDOW_SC } } - ctx->style.cursor_active = ctx->style.cursors[NK_CURSOR_RESIZE_TOP_RIGHT_DOWN_LEFT]; + int icon = //< @r-lyeh + ((layout->flags & NK_WINDOW_SCALE_TOP) && !(layout->flags & NK_WINDOW_SCALE_LEFT)) + || + ((layout->flags & NK_WINDOW_SCALE_LEFT) && !(layout->flags & NK_WINDOW_SCALE_TOP)) + ? NK_CURSOR_RESIZE_TOP_LEFT_DOWN_RIGHT : NK_CURSOR_RESIZE_TOP_RIGHT_DOWN_LEFT; + ctx->style.cursor_active = ctx->style.cursors[icon]; //< @r-lyeh in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.x = scaler.x + scaler.w/2.0f; in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.y = scaler.y + scaler.h/2.0f; } @@ -331064,6 +331383,499 @@ API void ProgressiveMesh(int vert_n, int vert_stride, const float *v, int tri_n, * SOFTWARE. */ #line 0 +#define expr expr2 // 3rd_lua.h +#line 1 "engine/split/3rd_eval.h" +/* A mathematical expression evaluator. + * It uses a recursive descent parser internally. + * Author: Werner Stoop + * This is free and unencumbered software released into the public domain. + * http://unlicense.org/ + */ + +#include +#include +#include /* remember to compile with -lm */ +#include +#include +#include + +/* Special tokens used by the lexer function lex() + * they've been chosen as non-printable characters + * so that printable characters can be used for other + * purposes + */ +#define TOK_END 0 /* end of text */ +#define TOK_INI 1 /* Initial state */ +#define TOK_ID 2 /* identifier */ +#define TOK_NUM 3 /* number */ + +/* Types of errors */ + // 0 /* "no error" */ +#define ERR_MEMORY 1 /* "out of memory" */ +#define ERR_LEXER 2 /* "unknown token" */ +#define ERR_LONGID 3 /* "identifier too long" */ +#define ERR_VALUE 4 /* "value expected" */ +#define ERR_BRACKET 5 /* "missing ')'" */ +#define ERR_FUNC 6 /* "unknown function" */ +#define ERR_ARGS 7 /* "wrong number of arguments" */ +#define ERR_CONST 8 /* "unknown constant" */ + +/* Other definitions */ +#define MAX_ID_LEN 11 /* Max length of an identifier */ +#define OPERATORS "+-*/%(),^" /* Valid operators */ + +#define EVAL_PI 3.141592654 +#define EVAL_E 2.718281828 +#define EVAL_DEG (EVAL_PI/180) + +/* Internal structure for the parser/evaluator */ +struct eval { + + jmp_buf j; /* For error handling */ + + const char *p; /* Position in the text being parsed */ + + double *st; /* Stack */ + int st_size; /* Stack size */ + int sp; /* Stack pointer */ + + /* The current and next tokens identified by the lexer */ + struct { + int type; /* Type of the token */ + double n_val; /* Numeric value of the previous lexed token */ + char s_val[MAX_ID_LEN]; /* String (identifier) value of the previous lexed token */ + } token[2]; + + int cur_tok; /* Current token, either 0 or 1 (see the comments of lex()) */ +}; + +/* Prototypes */ +static double pop(struct eval *ev); +static void push(struct eval *ev, double d); +static int lex(struct eval *ev); + +/* Prototypes for the recursive descent parser */ +static void expr(struct eval *ev); +static void add_expr(struct eval *ev); +static void mul_expr(struct eval *ev); +static void pow_expr(struct eval *ev); +static void uni_expr(struct eval *ev); +static void bra_expr(struct eval *ev); +static void id_expr(struct eval *ev); +static void num_expr(struct eval *ev); + +/* + * Evaluates a mathemeatical expression + */ +double eval(const char *exp/*, int *ep*/) { +int _ep, *ep = &_ep; + struct eval ev; + double ans = 0.0; + + assert(ep != NULL); + + /* Allocate a stack */ + ev.st_size = 10; + ev.st = CALLOC(ev.st_size, sizeof *ev.st); + if(!ev.st) + { + *ep = ERR_MEMORY; + return NAN; //0.0; + } + ev.sp = 0; + + /* Manage errors */ + *ep = setjmp(ev.j); + if(*ep != 0) + { + FREE(ev.st); + return NAN; //0.0; + } + + /* Initialize the lexer */ + ev.token[0].type = TOK_INI; + ev.token[0].s_val[0] = '\0'; + ev.token[1].type = TOK_INI; + ev.token[1].s_val[0] = '\0'; + ev.cur_tok = 0; + + /* Initialize the parser */ + ev.p = exp; + + /* lex once to initialize the lexer */ + if(lex(&ev) != TOK_END) + { + expr(&ev); + ans = pop(&ev); + } + + FREE(ev.st); + return ans; +} + +/* + * Pushes a value onto the stack, increases the stack size if necessary + */ +static void push(struct eval *ev, double d) { + if(ev->sp == ev->st_size) { + /* Resize the stack by 1.5 */ + double *old = ev->st; + int new_size = ev->st_size + (ev->st_size >> 1); + ev->st = REALLOC(ev->st, new_size); + if(!ev->st) { + ev->st = old; + longjmp(ev->j, ERR_MEMORY); + } + + ev->st_size = new_size; + } + + ev->st[ev->sp++] = d; +} + +// Pops a value from the top of the stack +static double pop(struct eval *ev) { + assert(ev->sp > 0); + return ev->st[--ev->sp]; +} + +// stricmp() is common, but not standard, so I provide my own +static int istrcmp(const char *p, const char *q) { + for(; tolower(p[0]) == tolower(q[0]) && p[0]; p++, q++); + return tolower(p[0]) - tolower(q[0]); +} + +/* + * Lexical analyzer function + * + * In order to implement LL(1), struct eval has an array of two token structures, + * and its cur_tok member is used to point to the _current_ token, while the other + * element contains the _next_ token. This implements a 2 element ring buffer where + * the lexer always writes to the _next_ token so that the recursive descent parser can + * _peek_ at the next token. + */ + +static int lex(struct eval *ev) { + int next_tok; + +start: + /* Cycle the tokens */ + next_tok = ev->cur_tok; + ev->cur_tok = ev->cur_tok?0:1; + + while(isspace(ev->p[0])) ev->p++; + + if(!ev->p[0]) { + /* End of the expression */ + ev->token[next_tok].type = TOK_END; + goto end; + } + else if(isdigit(ev->p[0]) || ev->p[0] == '.') { + /* Number */ + char *endp; + ev->token[next_tok].type = TOK_NUM; + ev->token[next_tok].n_val = strtod(ev->p, &endp); + ev->p = endp; + goto end; + } + else if(isalpha(ev->p[0])) { + /* Identifier */ + int i; + for(i = 0; isalnum(ev->p[0]) && i < MAX_ID_LEN - 1; i++, ev->p++) + ev->token[next_tok].s_val[i] = ev->p[0]; + + if(isalpha(ev->p[0])) longjmp(ev->j, ERR_LONGID); + + ev->token[next_tok].s_val[i] = '\0'; + ev->token[next_tok].type = TOK_ID; + goto end; + } + else if(strchr(OPERATORS, ev->p[0])) { + /* Operator */ + ev->token[next_tok].type = ev->p[0]; + ev->p++; + goto end; + } + else /* Unknown token */ + longjmp(ev->j, ERR_LEXER); + +end: + + /* If this was the first call, cycle the tokens again */ + if(ev->token[ev->cur_tok].type == TOK_INI) + goto start; + + return ev->token[ev->cur_tok].type; +} + +#define EVAL_TYPE(e) (e->token[e->cur_tok].type) +#define EVAL_ERROR(c) longjmp(ev->j, (c)) + +// num_expr ::= NUMBER +static void num_expr(struct eval *ev) { + if(EVAL_TYPE(ev) != TOK_NUM) + EVAL_ERROR(ERR_VALUE); + push(ev, ev->token[ev->cur_tok].n_val); + lex(ev); +} + +// expr ::= add_expr +static void expr(struct eval *ev) { + add_expr(ev); +} + +// add_expr ::= mul_expr [('+'|'-') mul_expr] +static void add_expr(struct eval *ev) { + int t; + mul_expr(ev); + while((t =EVAL_TYPE(ev)) == '+' || t == '-') { + double a,b; + lex(ev); + mul_expr(ev); + b = pop(ev); + a = pop(ev); + + if(t == '+') + push(ev, a + b); + else + push(ev, a - b); + } +} + +// mul_expr ::= pow_expr [('*'|'/'|'%') pow_expr] +static void mul_expr(struct eval *ev) { + int t; + pow_expr(ev); + while((t = EVAL_TYPE(ev)) == '*' || t == '/' || t == '%') { + double a,b; + lex(ev); + pow_expr(ev); + b = pop(ev); + a = pop(ev); + + if(t == '*') + push(ev, a * b); + else if(t == '/') + push(ev, a / b); + else + push(ev, fmod(a, b)); + } +} + +// pow_expr ::= uni_expr ['^' pow_expr] +static void pow_expr(struct eval *ev) { + /* Note that exponentiation is right associative: + 2^3^4 is 2^(3^4), not (2^3)^4 */ + uni_expr(ev); + if(EVAL_TYPE(ev) == '^') { + double a,b; + lex(ev); + pow_expr(ev); + b = pop(ev); + a = pop(ev); + push(ev, pow(a,b)); + } +} + +// uni_expr ::= ['+'|'-'] bra_expr +static void uni_expr(struct eval *ev) { + int t = '+'; + if(EVAL_TYPE(ev) == '-' || EVAL_TYPE(ev) == '+') { + t = EVAL_TYPE(ev); + lex(ev); + } + + bra_expr(ev); + + if(t == '-') { + double a = pop(ev); + push(ev, -a); + } +} + +// bra_expr ::= '(' add_expr ')' | id_expr +static void bra_expr(struct eval *ev) { + if(EVAL_TYPE(ev) == '(') { + lex(ev); + add_expr(ev); + if(EVAL_TYPE(ev) != ')') + EVAL_ERROR(ERR_BRACKET); + lex(ev); + } + else + id_expr(ev); +} + +// id_expr ::= ID '(' add_expr [',' add_expr]* ')' | ID | num_expr +static void id_expr(struct eval *ev) { + int nargs = 0; + char id[MAX_ID_LEN]; + if(EVAL_TYPE(ev) != TOK_ID) { + num_expr(ev); + } else { + strcpy(id, ev->token[ev->cur_tok].s_val); + lex(ev); + if(EVAL_TYPE(ev) != '(') { + /**/ if(!istrcmp(id, "true")) push(ev, 1.0); + else if(!istrcmp(id, "false")) push(ev, 0.0); + else if(!istrcmp(id, "on")) push(ev, 1.0); + else if(!istrcmp(id, "off")) push(ev, 0.0); + // pi - 3.141592654 + else if(!istrcmp(id, "pi")) + push(ev, EVAL_PI); + // e - base of natural logarithms, 2.718281828 + else if(!istrcmp(id, "e")) + push(ev, EVAL_E); + // deg - deg2rad, allows to degree conversion `sin(90*deg) = 1` + else if(!istrcmp(id, "deg")) + push(ev, EVAL_DEG); + else + EVAL_ERROR(ERR_CONST); + } else { + lex(ev); + + while(EVAL_TYPE(ev) != ')') { + add_expr(ev); + nargs++; + if(EVAL_TYPE(ev) == ')') break; + + if(EVAL_TYPE(ev) != ',') + EVAL_ERROR(ERR_BRACKET); + lex(ev); + } + lex(ev); + + // abs(x) - absolute value of x + if(!istrcmp(id, "abs")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, fabs(pop(ev))); + } + // ceil(x) - smallest integer greater than x + else if(!istrcmp(id, "ceil")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, ceil(pop(ev))); + } + // floor(x) - largest integer smaller than x + else if(!istrcmp(id, "floor")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, floor(pop(ev))); + } + // sin(x) - sine of x, in radians + else if(!istrcmp(id, "sin")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, sin(pop(ev))); + } + // asin(x) - arcsine of x, in radians + else if(!istrcmp(id, "asin")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, asin(pop(ev))); + } + // cos(x) - cosine of x, in radians + else if(!istrcmp(id, "cos")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, cos(pop(ev))); + } + // acos(x) - arccosine of x, in radians + else if(!istrcmp(id, "acos")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, acos(pop(ev))); + } + // tan(x) - tangent of x, in radians + else if(!istrcmp(id, "tan")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, tan(pop(ev))); + } + // atan(x) - arctangent of x, in radians + else if(!istrcmp(id, "atan")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, atan(pop(ev))); + } + // atan(y,x) - arctangent of y/x, in radians. + else if(!istrcmp(id, "atan2")) { + double a, b; + if(nargs != 2) EVAL_ERROR(ERR_ARGS); + b = pop(ev); + a = pop(ev); + push(ev, atan2(a,b)); + } + // sinh(x) - hyperbolic sine of x, in radians + else if(!istrcmp(id, "sinh")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, sinh(pop(ev))); + } + // cosh(x) - hyperbolic cosine of x, in radians + else if(!istrcmp(id, "cosh")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, cosh(pop(ev))); + } + // tanh(x) - hyperbolic tangent of x, in radians + else if(!istrcmp(id, "tanh")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, tanh(pop(ev))); + } + // log(x) - natural logarithm of x + else if(!istrcmp(id, "log")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, log(pop(ev))); + } + // log10(x) - logarithm of x, base-10 + else if(!istrcmp(id, "log10")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, log10(pop(ev))); + } + // exp(x) - computes e^x + else if(!istrcmp(id, "exp")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, exp(pop(ev))); + } + // sqrt(x) - square root of x + else if(!istrcmp(id, "sqrt")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, sqrt(pop(ev))); + } + // rad(x) - converts x from degrees to radians + else if(!istrcmp(id, "rad")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, pop(ev)*EVAL_PI/180); + } + // deg(x) - converts x from radians to degrees + else if(!istrcmp(id, "deg")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, pop(ev)*180/EVAL_PI); + } + // pow(x,y) - computes x^y + else if(!istrcmp(id, "pow")) { + double a, b; + if(nargs != 2) EVAL_ERROR(ERR_ARGS); + b = pop(ev); + a = pop(ev); + push(ev, pow(a,b)); + } + // hypot(x,y) - computes sqrt(x*x + y*y) + else if(!istrcmp(id, "hypot")) { + double a, b; + if(nargs != 2) EVAL_ERROR(ERR_ARGS); + b = pop(ev); + a = pop(ev); + push(ev, sqrt(a*a + b*b)); + } + else + EVAL_ERROR(ERR_FUNC); + } + } +} + +// + +#ifdef EVALDEMO +#include +int main() { + assert( eval("1+1") == 2 ); // common path + assert( eval("1+") != eval("1+") ); // check that errors return NAN + assert(~puts("Ok") ); +} +#endif +#line 0 // #define SQLITE_OMIT_LOAD_EXTENSION // #define SQLITE_CORE 1 // #define SQLITE_DEBUG 1 @@ -338836,7 +339648,7 @@ void font_color(const char *tag, uint32_t color) { if( f->initialized ) { glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_1D, f->texture_colors); - glTexSubImage1D(GL_TEXTURE_1D, 0, 0, FONT_MAX_COLORS, GL_BGRA, GL_UNSIGNED_BYTE, font_palette); + glTexSubImage1D(GL_TEXTURE_1D, 0, 0, FONT_MAX_COLORS, GL_RGBA, GL_UNSIGNED_BYTE, font_palette); } } } @@ -339034,7 +339846,7 @@ void font_face_from_mem(const char *tag, const void *ttf_bufferv, unsigned ttf_l glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // last chance to inspect the font atlases - if( flag("--debug-font-atlas") ) + if( flag("--font-debug") ) stbi_write_png(va("debug_font_atlas%d.png", index), f->width, f->height, 1, bitmap, 0); FREE(bitmap); @@ -339082,7 +339894,7 @@ void font_face_from_mem(const char *tag, const void *ttf_bufferv, unsigned ttf_l glGenTextures(1, &f->texture_colors); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_1D, f->texture_colors); - glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, FONT_MAX_COLORS, 0, GL_BGRA, GL_UNSIGNED_BYTE, font_palette); + glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, FONT_MAX_COLORS, 0, GL_RGBA, GL_UNSIGNED_BYTE, font_palette); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT); @@ -340398,9 +341210,6 @@ float ease_inout_bounce(float t) { return t < 0.5f ? 0.5f*ease_in_bounce(t*2) : float ease_inout_perlin(float t) { float t3=t*t*t,t4=t3*t,t5=t4*t; return 6*t5-15*t4+10*t3; } -float ease_ping_pong(float t, float(*fn1)(float), float(*fn2)(float)) { return t < 0.5 ? fn1(t*2) : fn2(1-(t-0.5)*2); } -float ease_pong_ping(float t, float(*fn1)(float), float(*fn2)(float)) { return 1 - ease_ping_pong(t,fn1,fn2); } - float ease(float t01, unsigned mode) { typedef float (*easing)(float); easing modes[] = { @@ -340445,6 +341254,10 @@ float ease(float t01, unsigned mode) { return modes[clampi(mode, 0, countof(modes))](clampf(t01,0,1)); } +float ease_pong(float t, unsigned fn) { return 1 - ease(t, fn); } +float ease_ping_pong(float t, unsigned fn1, unsigned fn2) { return t < 0.5 ? ease(t*2,fn1) : ease(1-(t-0.5)*2,fn2); } +float ease_pong_ping(float t, unsigned fn1, unsigned fn2) { return 1 - ease_ping_pong(t,fn1,fn2); } + // ---------------------------------------------------------------------------- float deg (float radians) { return radians / C_PI * 180.0f; } @@ -341239,6 +342052,14 @@ void printq( quat q ) { print_(&q.x,4,1); } void print33( float *m ) { print_(m,3,3); } void print34( float *m ) { print_(m,3,4); } void print44( float *m ) { print_(m,4,4); } + +// ----------- + +AUTORUN { + STRUCT( vec3, float, x ); + STRUCT( vec3, float, y ); + STRUCT( vec3, float, z, "Up" ); +} #line 0 #line 1 "engine/split/v4k_memory.c" @@ -342323,6 +343144,51 @@ void network_rpc_send(unsigned id, const char *cmdline) { #line 0 #line 1 "engine/split/v4k_pack.c" +// ----------------------------------------------------------------------------- +// semantic versioning in a single byte (octal) +// - rlyeh, public domain. +// +// - single octal byte that represents semantic versioning (major.minor.patch). +// - allowed range [0000..0377] ( <-> [0..255] decimal ) +// - comparison checks only major.minor tuple as per convention. + +int semver( int major, int minor, int patch ) { + return SEMVER(major, minor, patch); +} +int semvercmp( int v1, int v2 ) { + return SEMVERCMP(v1, v2); +} + +#if 0 +AUTORUN { + for( int i= 0; i <= 255; ++i) printf(SEMVERFMT ",", i); + puts(""); + + printf(SEMVERFMT "\n", semver(3,7,7)); + printf(SEMVERFMT "\n", semver(2,7,7)); + printf(SEMVERFMT "\n", semver(1,7,7)); + printf(SEMVERFMT "\n", semver(0,7,7)); + + printf(SEMVERFMT "\n", semver(3,7,1)); + printf(SEMVERFMT "\n", semver(2,5,3)); + printf(SEMVERFMT "\n", semver(1,3,5)); + printf(SEMVERFMT "\n", semver(0,1,7)); + + assert( semvercmp( 0357, 0300 ) > 0 ); + assert( semvercmp( 0277, 0300 ) < 0 ); + assert( semvercmp( 0277, 0200 ) > 0 ); + assert( semvercmp( 0277, 0100 ) < 0 ); + assert( semvercmp( 0076, 0070 ) == 0 ); + assert( semvercmp( 0076, 0077 ) == 0 ); + assert( semvercmp( 0176, 0170 ) == 0 ); + assert( semvercmp( 0176, 0177 ) == 0 ); + assert( semvercmp( 0276, 0270 ) == 0 ); + assert( semvercmp( 0276, 0277 ) == 0 ); + assert( semvercmp( 0376, 0370 ) == 0 ); + assert( semvercmp( 0376, 0377 ) == 0 ); +} +#endif + // ----------------------------------------------------------------------------- // compile-time fourcc, eightcc @@ -342350,24 +343216,18 @@ char *cc8str(uint64_t x) { // ---------------------------------------------------------------------------- // float conversion (text) -vec2 atof2(const char *s) { - vec2 v = {0}; - sscanf(s, "%f,%f", &v.x, &v.y); - return v; +char* itoa1(int v) { + return va("%d", v); } -vec3 atof3(const char *s) { - vec3 v = {0}; - sscanf(s, "%f,%f,%f", &v.x, &v.y, &v.z); - return v; +char* itoa2(vec2i v) { + return va("%d,%d", v.x,v.y); } -vec4 atof4(const char *s) { - vec4 v = {0}; - sscanf(s, "%f,%f,%f,%f", &v.x, &v.y, &v.z, &v.w); - return v; +char* itoa3(vec3i v) { + return va("%d,%d,%d", v.x,v.y,v.z); } -char* ftoa(float f) { - return va("%f", f); +char* ftoa1(float v) { + return va("%f", v); } char* ftoa2(vec2 v) { return va("%f,%f", v.x, v.y); @@ -342379,6 +343239,51 @@ char* ftoa4(vec4 v) { return va("%f,%f,%f,%f", v.x, v.y, v.z, v.w); } +float atof1(const char *s) { + char buf[64]; + return sscanf(s, "%64[^]\r\n,}]", buf) == 1 ? (float)eval(buf) : (float)NAN; +} +vec2 atof2(const char *s) { + vec2 v = { 0 }; + char buf1[64],buf2[64]; + int num = sscanf(s, "%64[^]\r\n,}],%64[^]\r\n,}]", buf1, buf2); + if( num > 0 ) v.x = eval(buf1); + if( num > 1 ) v.y = eval(buf2); + return v; +} +vec3 atof3(const char *s) { + vec3 v = {0}; + char buf1[64],buf2[64],buf3[64]; + int num = sscanf(s, "%64[^]\r\n,}],%64[^]\r\n,}],%64[^]\r\n,}]", buf1, buf2, buf3); + if( num > 0 ) v.x = eval(buf1); + if( num > 1 ) v.y = eval(buf2); + if( num > 2 ) v.z = eval(buf3); + return v; +} +vec4 atof4(const char *s) { + vec4 v = {0}; + char buf1[64],buf2[64],buf3[64],buf4[64]; + int num = sscanf(s, "%64[^]\r\n,}],%64[^]\r\n,}],%64[^]\r\n,}],%64[^]\r\n,}]", buf1, buf2, buf3, buf4); + if( num > 0 ) v.x = eval(buf1); + if( num > 1 ) v.y = eval(buf2); + if( num > 2 ) v.z = eval(buf3); + if( num > 3 ) v.w = eval(buf4); + return v; +} + +// @todo: expand this to proper int parsers +int atoi1(const char *s) { + return (int)atof1(s); +} +vec2i atoi2(const char *s) { + vec2 v = atof2(s); + return vec2i( v.x, v.y ); +} +vec3i atoi3(const char *s) { + vec3 v = atof3(s); + return vec3i( v.x, v.y, v.z ); +} + // endianness ----------------------------------------------------------------- // - rlyeh, public domain @@ -343939,10 +344844,10 @@ void *member_findptr(void *obj, const char *T, const char *M) { M = symbol(M); return (char*)obj + member_find(T,M).sz; } -array(reflect_t) members_find(const char *T) { +array(reflect_t)* members_find(const char *T) { reflect_init(); T = symbol(T); - return *map_find(members, intern(T)); + return map_find(members, intern(T)); } @@ -344558,13 +345463,13 @@ void shader_colormap(const char *name, colormap_t c ) { // colors unsigned rgba( uint8_t r, uint8_t g, uint8_t b, uint8_t a ) { - return (unsigned)r << 24 | g << 16 | b << 8 | a; + return (unsigned)a << 24 | b << 16 | g << 8 | r; } unsigned bgra( uint8_t b, uint8_t g, uint8_t r, uint8_t a ) { return rgba(r,g,b,a); } -float alpha( unsigned rgba ) { - return ( rgba >> 24 ) / 255.f; +unsigned alpha( unsigned rgba ) { + return rgba >> 24; } unsigned rgbaf(float r, float g, float b, float a) { @@ -350927,7 +351832,7 @@ void trap_on_abort(int signal) { exit(-1); } void trap_on_debug(int signal) { - breakpoint("Error: unexpected signal"); + alert("Error: unexpected signal"), breakpoint(); fprintf(stderr, "Error: unexpected signal %s (%d)\n%s\n", trap_name(signal), signal, callstack(16)); exit(-1); } @@ -351197,7 +352102,7 @@ void tty_color(unsigned color) { } #endif if( color ) { - // if( color == RED ) breakpoint("break on RED"); // debug + // if( color == RED ) alert("break on error message (RED)"), breakpoint(); // debug unsigned r = (color >> 16) & 255; unsigned g = (color >> 8) & 255; unsigned b = (color >> 0) & 255; @@ -351316,6 +352221,9 @@ static void debugbreak(void) { // break if debugger present #endif void alert(const char *message) { // @todo: move to app_, besides die() + window_visible(false); + message = message[0] == '!' ? (const char*)va("%s\n%s", message+1, callstack(+48)) : message; + #if is(win32) MessageBoxA(0, message, 0,0); #elif is(ems) @@ -351326,18 +352234,12 @@ void alert(const char *message) { // @todo: move to app_, besides die() #elif is(osx) system(va("osascript -e 'display alert \"Alert\" message \"%s\"'", message)); #endif -} -void breakpoint(const char *reason) { - window_visible(false); - if( reason ) { - const char *fulltext = reason[0] == '!' ? va("%s\n%s", reason+1, callstack(+48)) : reason; - PRINTF("%s", fulltext); - - (alert)(fulltext); - } - debugbreak(); window_visible(true); + } + +void breakpoint() { + debugbreak(); } bool has_debugger() { @@ -351400,7 +352302,9 @@ int (PANIC)(const char *error, const char *file, int line) { tty_color(0); - breakpoint(error); + alert(error); + breakpoint(); + exit(-line); return 1; } @@ -351552,11 +352456,13 @@ const char* app_savefile() { // ---------------------------------------------------------------------------- // tests -static __thread int test_oks, test_errs, test_once; -static void test_exit(void) { fprintf(stderr, "%d/%d tests passed\n", test_oks, test_oks+test_errs); } +static __thread int test_oks, test_errors, test_once; +static void test_exit(void) { fprintf(stderr, "%d/%d tests passed\n", test_oks, test_oks+test_errors); } int (test)(const char *file, int line, const char *expr, bool result) { + static int breakon = -1; if(breakon<0) breakon = optioni("--test-break", 0); + if( breakon == (test_oks+test_errors+1) ) alert("user requested to break on this test"), breakpoint(); test_once = test_once || !(atexit)(test_exit); - test_oks += result, test_errs += !result; + test_oks += result, test_errors += !result; return (result || (tty_color(RED), fprintf(stderr, "(Test `%s` failed %s:%d)\n", expr, file, line), tty_color(0), 0) ); } #line 0 @@ -351739,7 +352645,6 @@ bool id_valid(uintptr_t id) { #define UI_FONT_ENUM(carlito,b612) b612 // carlito -#define UI_FONT_ICONS "MaterialIconsSharp-Regular.otf" // "MaterialIconsOutlined-Regular.otf" "MaterialIcons-Regular.ttf" // #define UI_FONT_REGULAR UI_FONT_ENUM("Carlito", "B612") "-Regular.ttf" #define UI_FONT_HEADING UI_FONT_ENUM("Carlito", "B612") "-BoldItalic.ttf" #define UI_FONT_TERMINAL UI_FONT_ENUM("Inconsolata", "B612Mono") "-Regular.ttf" @@ -351810,12 +352715,16 @@ static void nk_config_custom_fonts() { } // ...with icons embedded on it. - - for( char *data = vfs_load(UI_FONT_ICONS, &datalen); data; data = 0 ) { - static const nk_rune icon_range[] = {UI_ICON_MIN, UI_ICON_MED /*MAX*/, 0}; - + static struct icon_font { + const char *file; nk_rune range[3]; + } icons[] = { + {"MaterialIconsSharp-Regular.otf", {UI_ICON_MIN, UI_ICON_MED /*MAX*/, 0}}, // "MaterialIconsOutlined-Regular.otf" "MaterialIcons-Regular.ttf" + {"materialdesignicons-webfont.ttf", {0xF68C /*ICON_MIN_MDI*/, 0xF1C80/*ICON_MAX_MDI*/, 0}}, + }; + for( int f = 0; f < countof(icons); ++f ) + for( char *data = vfs_load(icons[f].file, &datalen); data; data = 0 ) { struct nk_font_config cfg = nk_font_config(UI_ICON_FONTSIZE); - cfg.range = icon_range; // nk_font_default_glyph_ranges(); + cfg.range = icons[f].range; // nk_font_default_glyph_ranges(); cfg.merge_mode = 1; cfg.spacing.x += UI_ICON_SPACING_X; @@ -354760,7 +355669,8 @@ void glNewFrame() { } bool window_create_from_handle(void *handle, float scale, unsigned flags) { - ifdef(debug, if( flag("--tests") ) exit(0)); + // abort run if any test suite failed in unit-test mode + ifdef(debug, if( flag("--test-only") ) exit( test_errors ? -test_errors : 0 )); glfw_init(); v4k_init(); @@ -355772,50 +356682,807 @@ int window_has_maximize() { #line 0 #line 1 "engine/split/v4k_obj.c" -// ----------------------------------------------------------------------------- -// semantic versioning in a single byte (octal) +// C objects framework // - rlyeh, public domain. - // -// - single octal byte that represents semantic versioning (major.minor.patch). -// - allowed range [0000..0377] ( <-> [0..255] decimal ) -// - comparison checks only major.minor tuple as per convention. -int semver( int major, int minor, int patch ) { - return SEMVER(major, minor, patch); +// --- implement new vtables + +obj_vtable(ctor, void, {} ); +obj_vtable(dtor, void, {} ); + +obj_vtable_null(save, char* ); +obj_vtable_null(load, bool ); +obj_vtable_null(test, int ); + +obj_vtable_null(init, int ); +obj_vtable_null(quit, int ); +obj_vtable_null(tick, int ); +obj_vtable_null(draw, int ); + +obj_vtable_null(lerp, int ); +obj_vtable_null(edit, int ); // OSC cmds: argc,argv "undo","redo","cut","copy","paste","edit","view","menu" + +// ---------------------------------------------------------------------------- + +const char *OBJTYPES[256] = { 0 }; // = { REPEAT256("") }; + +// ---------------------------------------------------------------------------- +// heap/stack ctor/dtor + +void *obj_malloc(unsigned sz) { + //sz = sizeof(obj) + sz + sizeof(array(obj*))); // useful? + obj *ptr = CALLOC(1, sz); + OBJ_CTOR_HDR(ptr,1,intern("obj"),sz,OBJTYPE_obj); + return ptr; } -int semvercmp( int v1, int v2 ) { - return SEMVERCMP(v1, v2); +void *obj_free(void *o) { + if( !((obj*)o)->objrefs ) { + obj_dtor(o); + //obj_zero(o); + if( ((obj*)o)->objheap ) { + FREE(o); + } + return 0; +} + return o; // cannot destroy: object is still referenced } -#if 0 -AUTORUN { - for( int i= 0; i <= 255; ++i) printf(SEMVERFMT ",", i); - puts(""); +// ---------------------------------------------------------------------------- +// core - printf(SEMVERFMT "\n", semver(3,7,7)); - printf(SEMVERFMT "\n", semver(2,7,7)); - printf(SEMVERFMT "\n", semver(1,7,7)); - printf(SEMVERFMT "\n", semver(0,7,7)); - - printf(SEMVERFMT "\n", semver(3,7,1)); - printf(SEMVERFMT "\n", semver(2,5,3)); - printf(SEMVERFMT "\n", semver(1,3,5)); - printf(SEMVERFMT "\n", semver(0,1,7)); - - assert( semvercmp( 0357, 0300 ) > 0 ); - assert( semvercmp( 0277, 0300 ) < 0 ); - assert( semvercmp( 0277, 0200 ) > 0 ); - assert( semvercmp( 0277, 0100 ) < 0 ); - assert( semvercmp( 0076, 0070 ) == 0 ); - assert( semvercmp( 0076, 0077 ) == 0 ); - assert( semvercmp( 0176, 0170 ) == 0 ); - assert( semvercmp( 0176, 0177 ) == 0 ); - assert( semvercmp( 0276, 0270 ) == 0 ); - assert( semvercmp( 0276, 0277 ) == 0 ); - assert( semvercmp( 0376, 0370 ) == 0 ); - assert( semvercmp( 0376, 0377 ) == 0 ); +uintptr_t obj_header(const void *o) { + return ((obj*)o)->objheader; } +uintptr_t obj_id(const void *o) { + return ((obj*)o)->objid; +} +unsigned obj_typeid(const void *o) { + return ((obj*)o)->objtype; +} +const char *obj_type(const void *o) { + return OBJTYPES[ (((obj*)o)->objtype) ]; +} +const char *obj_name(const void *o) { + return quark(((obj*)o)->objnameid); +} +int obj_sizeof(const void *o) { + return (int)( ((const obj*)o)->objsizew << OBJ_MIN_PRAGMAPACK_BITS ); +} +int obj_size(const void *o) { // size of all members together in struct. may include padding bytes. + static int obj_sizes[256] = {0}; + unsigned objtypeid = ((obj*)o)->objtype; + if( objtypeid > 1 && !obj_sizes[objtypeid] ) { // check reflection for a more accurate objsize (without padding bits) + reflect_init(); + array(reflect_t) *found = map_find(members, intern(obj_type(o))); + if(!found) + obj_sizes[objtypeid] = obj_sizeof(o) - sizeof(obj); // @fixme: -= sizeof(entity); + else + for each_array_ptr(*found, reflect_t, it) + obj_sizes[objtypeid] += it->bytes; + } + return obj_sizes[objtypeid]; +} +char *obj_data(void *o) { // pointer to the first member in struct + return (char*)o + sizeof(obj); +} +const char *obj_datac(const void *o) { // const pointer to the first struct member + return (const char*)o + sizeof(obj); +} +void* obj_payload(const void *o) { // pointer right after last member in struct + return (char*)o + (((obj*)o)->objsizew<objrefs; + ++o->objrefs; + assert( num < o->objrefs && "Object referenced too many times"); + //++global_ref_count; + return o; +} +void *obj_unref(void *oo) { + obj* o = (obj*)oo; + if( o->objrefs ) --o->objrefs; + if( o->objrefs ) return o; + obj_free(o); + //--global_ref_count; + return 0; +} + +// ---------------------------------------------------------------------------- +// scene tree + +array(obj*)* obj_children(const void *o) { + array(obj*) *c = obj_payload(o); + if(!(*c)) array_push((*c), NULL); // default parenting: none. @todo: optimize & move this at construction time + return c; +} +obj* obj_parent(const void *o) { + array(obj*) *c = obj_children(o); + return (*c) ? 0[*c] : NULL; +} +obj* obj_root(const void *o) { + while( obj_parent(o) ) o = obj_parent(o); + return (obj*)o; +} +array(obj*)* obj_siblings(const void *o) { + return obj_children(obj_parent(o)); +} + +static +obj* obj_reparent(obj *o, const void *p) { + array(obj*) *c = obj_children(o); + 0[*c] = (void*)p; + return o; +} + +obj* obj_detach(void *c) { + obj *p = obj_parent(c); + if( p ) { + uintptr_t id = obj_id(c); + + array(obj*) *oo = obj_children(p); + for( int i = 1, end = array_count(*oo); i < end; ++i) { + obj *v = (*oo)[i]; + { + if( obj_id(v) == id ) { + obj_reparent(c, 0); + return c; + } + } + } + } + return 0; +} +obj* obj_attach(void *o, void *c) { + // reattach + obj_detach(c); + obj_reparent(c, o); + // insert into children + array(obj*) *p = obj_children(o); + array_push(*p, c); + return o; +} + +int obj_dumptree(const void *o) { + static int tabs = 0; + printf("%*s" "+- %s\n", tabs++, "", obj_name(o)); + for each_objchild(o, obj, v) { + obj_dumptree(v); + } + --tabs; + return 0; +} + +static +void test_obj_scene() { + obj *r = obj_new_ext(obj, "root"); // root + obj *c1 = obj_new_ext(obj, "child1"); // child1 + obj *c2 = obj_new_ext(obj, "child2"); // child2 + obj *gc1 = obj_new_ext(obj, "grandchild1"); // grandchild1 + obj *gc2 = obj_new_ext(obj, "grandchild2"); // grandchild2 + obj *gc3 = obj_new_ext(obj, "grandchild3"); // grandchild3 + + test( !obj_parent(r) ); + test( !obj_parent(c1) ); + test( !obj_parent(c2) ); + test( !obj_parent(gc1) ); + test( !obj_parent(gc2) ); + test( !obj_parent(gc3) ); + test( obj_root(r) == r ); + test( obj_root(c1) == c1 ); + test( obj_root(c2) == c2 ); + test( obj_root(gc1) == gc1 ); + test( obj_root(gc2) == gc2 ); + test( obj_root(gc3) == gc3 ); + + // r + obj_attach(r, c1); // +- c1 + obj_attach(c1, gc1); // +- gc1 + obj_attach(r, c2); // +- c2 + obj_attach(c2, gc2); // +- gc2 + obj_attach(c2, gc3); // +- gc3 + + // obj_dumptree(r); + // puts("---"); + + test( obj_parent(r) == 0 ); + test( obj_parent(c1) == r ); + test( obj_parent(c2) == r ); + test( obj_parent(gc1) == c1 ); + test( obj_parent(gc2) == c2 ); + test( obj_parent(gc3) == c2 ); + + test( obj_root(r) == r ); + test( obj_root(c1) == r ); + test( obj_root(c2) == r ); + test( obj_root(gc1) == r ); + test( obj_root(gc2) == r ); + test( obj_root(gc3) == r ); + + for each_objchild(r, obj, o) test( o == c1 || o == c2 ); + for each_objchild(c1, obj, o) test( o == gc1 ); + for each_objchild(c2, obj, o) test( o == gc2 || o == gc3 ); + + obj_detach(c1); + test( !obj_parent(c1) ); + for each_objchild(r, obj, o) test( o != c1 ); + for each_objchild(c1, obj, o) test( o == gc1 ); + + obj_detach(c2); + test( !obj_parent(c2) ); + for each_objchild(r, obj, o) test( o != c2 ); + for each_objchild(c2, obj, o) test( o == gc2 || o == gc3 ); +} + +// ---------------------------------------------------------------------------- +// metadata + +static map(int,int) oms; +static thread_mutex_t *oms_lock; +const char *obj_metaset(const void *o, const char *key, const char *value) { + do_threadlock(oms_lock) { + if(!oms) map_init_int(oms); + int *q = map_find_or_add(oms, intern(va("%llu-%s",obj_id((obj*)o),key)), 0); + if(!*q && !value[0]) {} else *q = intern(value); + return quark(*q); + } + return 0; // unreachable +} +const char* obj_metaget(const void *o, const char *key) { + do_threadlock(oms_lock) { + if(!oms) map_init_int(oms); + int *q = map_find_or_add(oms, intern(va("%llu-%s",obj_id((obj*)o),key)), 0); + return quark(*q); + } + return 0; // unreachable +} + +static +void test_obj_metadatas( void *o1 ) { + obj *o = (obj *)o1; + test( !strcmp("", obj_metaget(o, "has_passed_test")) ); + test( !strcmp("yes", obj_metaset(o, "has_passed_test", "yes")) ); + test( !strcmp("yes", obj_metaget(o, "has_passed_test")) ); +} + +// ---------------------------------------------------------------------------- +// stl + +void* obj_swap(void *dst, void *src) { // @testme + int len = obj_size(dst); + char *buffer = ALLOCA(len); + memcpy(buffer, obj_datac(dst), len); + memcpy(obj_data(dst), obj_datac(src), len); + memcpy(obj_data(src), buffer, len); + return dst; +} + +void* obj_copy_fast(void *dst, const void *src) { + // note: prefer obj_copy() as it should handle pointers and guids as well + return memcpy(obj_data(dst), obj_datac(src), obj_size(dst)); +} +void* obj_copy(void *dst, const void *src) { // @testme + // @todo: use obj_copy_fast() silently if the object does not contain any pointers/guids + return obj_loadini(dst, obj_saveini(src)); + // return obj_load(dst, obj_save(src)); + // return obj_loadbin(dst, obj_savebin(src)); + // return obj_loadini(dst, obj_saveini(src)); + // return obj_loadjson(dst, obj_savejson(src)); + // return obj_loadmpack(dst, obj_savempack(src)); +} + +int obj_comp_fast(const void *a, const void *b) { + // note: prefer obj_comp() as it should handle pointers and guids as well + return memcmp(obj_datac(a), obj_datac(b), obj_size(a)); +} +int obj_comp(const void *a, const void *b) { + // @todo: use obj_comp_fast() silently if the object does not contain any pointers/guids + return strcmp(obj_saveini(a),obj_saveini(b)); +} +int obj_lesser(const void *a, const void *b) { + return obj_comp(a,b) < 0; +} +int obj_greater(const void *a, const void *b) { + return obj_comp(a,b) > 0; +} +int obj_equal(const void *a, const void *b) { + return obj_comp(a,b) == 0; +} + +uint64_t obj_hash(const void *o) { + return hash_bin(obj_datac(o), obj_size(o)); +} + +static +void test_obj_similarity(void *o1, void *o2) { + obj *b = (obj*)o1; + obj *c = (obj*)o2; + test( 0 == strcmp(obj_name(b),obj_name(c)) ); + test( 0 == strcmp(obj_type(b),obj_type(c)) ); +} +static +void test_obj_equality(void *o1, void *o2) { + obj *b = (obj*)o1; + obj *c = (obj*)o2; + test_obj_similarity(b, c); + test( obj_size(b) == obj_size(c) ); + test( obj_hash(b) == obj_hash(c) ); + test( 0 == obj_comp(b,c) ); + test( obj_equal(b,c) ); + test( !obj_lesser(b,c) ); + test( !obj_greater(b,c) ); +} +static +void test_obj_exact(void *o1, void *o2) { + obj *b = (obj*)o1; + obj *c = (obj*)o2; + test_obj_equality(b, c); + test( obj_header(b) == obj_header(c) ); + test( 0 == memcmp(b, c, obj_sizeof(b)) ); +} + +// ---------------------------------------------------------------------------- +// debug + +bool obj_hexdump(const void *oo) { + const obj *o = (const obj *)oo; + int header = 1 * sizeof(obj); + printf("; name[%s] type[%s] id[%d..%d] unused[%08x] sizeof[%02d] %llx\n", + obj_name(o), obj_type(o), + (int)o->objid>>16, (int)o->objid&0xffff, (int)o->objunused, + obj_sizeof(o), o->objheader); + return hexdump(obj_datac(o) - header, obj_size(o) + header), 1; +} +int obj_print(const void *o) { + char *sav = obj_saveini(o); // obj_savejson(o) + return puts(sav); +} +static char *obj_tempname = 0; +static FILE *obj_filelog = 0; +int (obj_printf)(const void *o, const char *text) { + if( !obj_tempname ) { + obj_tempname = stringf("%s.log", app_name()); + unlink(obj_tempname); + obj_filelog = fopen(obj_tempname, "w+b"); + if( obj_filelog ) fseek(obj_filelog, 0L, SEEK_SET); + } + int rc = 0; + for( char *end; (end = strchr(text, '\n')) != NULL; ) { + rc |= fprintf(obj_filelog, "[%p] %.*s\n", o, (int)(end - text), text ); + text = end + 1; + } + if( text[0] ) rc |= fprintf(obj_filelog, "[%p] %s\n", o, text); + return rc; +} +int obj_console(const void *o) { // obj_output() ? + if( obj_filelog ) fflush(obj_filelog); + return obj_tempname && !system(va(ifdef(win32,"type \"%s\" | find \"[%p]\"", "cat %s | grep \"[%p]\""), obj_tempname, o)); +} + +static +void test_obj_console(void *o1) { + obj *o = (obj *)o1; + + obj_printf(o, "this is [%s], line 1\n", obj_name(o)); + obj_printf(NULL, "this line does not belong to any object\n"); + obj_printf(o, "this is [%s], line 2\n", obj_name(o)); + obj_console(o); +} + +// ---------------------------------------------------------------------------- +// serialization + +const char *p2s(const char *type, void *p) { + // @todo: p2s(int interned_type, void *p) + /**/ if( !strcmp(type, "int") ) return itoa1(*(int*)p); + else if( !strcmp(type, "unsigned") ) return itoa1(*(unsigned*)p); + else if( !strcmp(type, "float") ) return ftoa1(*(float*)p); + else if( !strcmp(type, "double") ) return ftoa1(*(double*)p); + else if( !strcmp(type, "uintptr_t") ) return va("%08llx", *(uintptr_t*)p); + else if( !strcmp(type, "vec2i") ) return itoa2(*(vec2i*)p); + else if( !strcmp(type, "vec3i") ) return itoa3(*(vec3i*)p); + else if( !strcmp(type, "vec2") ) return ftoa2(*(vec2*)p); + else if( !strcmp(type, "vec3") ) return ftoa3(*(vec3*)p); + else if( !strcmp(type, "vec4") ) return ftoa4(*(vec4*)p); + else if( !strcmp(type, "char*") || !strcmp(type, "string") ) return va("%s", *(char**)p); + // @todo: if strchr('*') assume obj, if reflected save guid: obj_id(); + return tty_color(YELLOW), printf("p2s: cannot serialize `%s` type\n", type), tty_color(0), ""; +} +bool s2p(void *P, const char *type, const char *str) { + int i; unsigned u; float f; double g; char *s = 0; uintptr_t p; + vec2 v2; vec3 v3; vec4 v4; vec2i v2i; vec3i v3i; + /**/ if( !strcmp(type, "int") ) return !!memcpy(P, (i = atoi1(str), &i), sizeof(i)); + else if( !strcmp(type, "unsigned") ) return !!memcpy(P, (u = atoi1(str), &u), sizeof(u)); + else if( !strcmp(type, "vec2i") ) return !!memcpy(P, (v2i = atoi2(str), &v2i), sizeof(v2i)); + else if( !strcmp(type, "vec3i") ) return !!memcpy(P, (v3i = atoi3(str), &v3i), sizeof(v3i)); + else if( !strcmp(type, "float") ) return !!memcpy(P, (f = atof1(str), &f), sizeof(f)); + else if( !strcmp(type, "double") ) return !!memcpy(P, (g = atof1(str), &g), sizeof(g)); + else if( !strcmp(type, "vec2") ) return !!memcpy(P, (v2 = atof2(str), &v2), sizeof(v2)); + else if( !strcmp(type, "vec3") ) return !!memcpy(P, (v3 = atof3(str), &v3), sizeof(v3)); + else if( !strcmp(type, "vec4") ) return !!memcpy(P, (v4 = atof4(str), &v4), sizeof(v4)); + else if( !strcmp(type, "uintptr_t") ) return !!memcpy(P, (p = strtol(str, NULL, 16), &p), sizeof(p)); + else if( !strcmp(type, "char*") || !strcmp(type, "string") ) { + char substring[128] = {0}; + sscanf(str, "%[^\r\n]", substring); + + strcatf(&s, "%s", substring); + + *(uintptr_t*)(P) = (uintptr_t)s; + return 1; + } + // @todo: if strchr('*') assume obj, if reflected load guid: obj_id(); + return tty_color(YELLOW), printf("s2p: cannot deserialize `%s` type\n", type), tty_color(0), 0; +} + +char *obj_saveini(const void *o) { // @testme + char *out = 0; + const char *T = obj_type(o); + strcatf(&out, "[%s] ; v100\n", T); + for each_member(T,R) { + const char *sav = p2s(R->type,(char*)(o)+R->sz); + if(!sav) return FREE(out), NULL; + strcatf(&out,"%s.%s=%s\n", R->type,R->name,sav ); + } + char *cpy = va("%s", out); + FREE(out); + return cpy; +} +obj *obj_mergeini(void *o, const char *ini) { // @testme + const char *sqr = strchr(ini, '['); + if( !sqr ) return 0; + ini = sqr+1; + + char T[64] = {0}; + if( sscanf(ini, "%64[^]]", &T) != 1 ) return 0; // @todo: parse version as well + ini += strlen(T); + + for each_member(T,R) { + char *lookup = va("\n%s.%s=", R->type,R->name), *found = 0; + + // type needed? /* + if(!found) { found = strstr(ini, lookup); if (found) found += strlen(lookup); } + if(!found) { *lookup = '\r'; } + if(!found) { found = strstr(ini, lookup); if (found) found += strlen(lookup); } + // */ + + if(!found) lookup = va("\n%s=", R->name); + + if(!found) { found = strstr(ini, lookup); if (found) found += strlen(lookup); } + if(!found) { *lookup = '\r'; } + if(!found) { found = strstr(ini, lookup); if (found) found += strlen(lookup); } + + if( found) { + if(!s2p((char*)(o)+R->sz, R->type, found)) + return 0; + } + } + return o; +} +obj *obj_loadini(void *o, const char *ini) { // @testme + return obj_mergeini(obj_zero(o), ini); +} + +char *obj_savejson(const void *o) { + char *j = 0; + const char *T = obj_type(o); + for each_member(T,R) { + const char *sav = p2s(R->type,(char*)(o)+R->sz); + if(!sav) return FREE(j), NULL; + char is_string = !strcmp(R->type,"char*") || !strcmp(R->type,"string"); + strcatf(&j," %s: %s%s%s,\n", R->name,is_string?"\"":"",sav,is_string?"\"":"" ); + } + char *out = va("%s: { // v100\n%s}\n", T,j); + FREE(j); +#if is(debug) + json5 root = { 0 }; + char *error = json5_parse(&root, va("%s", out), 0); + assert( !error ); + json5_free(&root); #endif + return out; +} +obj *obj_mergejson(void *o, const char *json) { + // @fixme: va() call below could be optimized out since we could figure it out if json was internally provided (via va or strdup), or user-provided + json5 root = { 0 }; + char *error = json5_parse(&root, va("%s", json), 0); // @todo: parse version comment + if( !error && root.type == JSON5_OBJECT && root.count == 1 ) { + json5 *n = &root.nodes[0]; + char *T = n->name; + for each_member(T,R) { + for( int i = 0; i < n->count; ++i ) { + if( !strcmp(R->name, n->nodes[i].name) ) { + void *p = (char*)o + R->sz; + /**/ if( n->nodes[i].type == JSON5_UNDEFINED ) {} + else if( n->nodes[i].type == JSON5_NULL ) { + *(uintptr_t*)(p) = (uintptr_t)0; + } + else if( n->nodes[i].type == JSON5_BOOL ) { + *(bool*)p = n->nodes[i].boolean; + } + else if( n->nodes[i].type == JSON5_INTEGER ) { + if( strstr(R->type, "64" ) ) + *(int64_t*)p = n->nodes[i].integer; + else + *(int*)p = n->nodes[i].integer; + } + else if( n->nodes[i].type == JSON5_STRING ) { + char *s = 0; + strcatf(&s, "%s", n->nodes[i].string); + *(uintptr_t*)(p) = (uintptr_t)s; + } + else if( n->nodes[i].type == JSON5_REAL ) { + if( R->type[0] == 'f' ) + *(float*)(p) = n->nodes[i].real; + else + *(double*)(p) = n->nodes[i].real; + } + else if( n->nodes[i].type == JSON5_OBJECT ) {} + else if( n->nodes[i].type == JSON5_ARRAY ) {} + break; + } + } + } + } + json5_free(&root); + return error ? 0 : o; +} +obj *obj_loadjson(void *o, const char *json) { // @testme + return obj_mergejson(obj_zero(o), json); +} + +char *obj_savebin(const void *o) { // PACKMSG("ss", "entity_v1", quark(self->objnameid)); // = PACKMSG("p", obj_data(&b), (uint64_t)obj_size(&b)); + int len = cobs_bounds(obj_size(o)); + char *sav = va("%*.s", len, ""); + len = cobs_encode(obj_datac(o), obj_size(o), sav, len); + sav[len] = '\0'; + return sav; +} +obj *obj_mergebin(void *o, const char *sav) { // UNPACKMSG(sav, "p", obj_data(c), (uint64_t)obj_size(c)); + int outlen = cobs_decode(sav, strlen(sav), obj_data(o), obj_size(o)); + return outlen != obj_size(o) ? NULL : o; +} +obj *obj_loadbin(void *o, const char *sav) { + return obj_mergebin(obj_zero(o), sav); +} + +char *obj_savempack(const void *o) { // @todo + return ""; +} +obj *obj_mergempack(void *o, const char *sav) { // @todo + return 0; +} +obj *obj_loadmpack(void *o, const char *sav) { // @todo + return obj_mergempack(obj_zero(o), sav); +} + +static __thread array(char*) obj_stack; +int obj_push(const void *o) { + char *bin = STRDUP(obj_savebin(o)); + array_push(obj_stack, bin); + return 1; +} +int obj_pop(void *o) { + char *bin = *array_pop(obj_stack); + int rc = !!obj_loadbin(o, bin); + return FREE(bin), rc; +} + +static +void test_obj_serialization(void *o1, void *o2) { + obj* b = (obj*)o1; + obj* c = (obj*)o2; + + char *json = obj_savejson(b); // puts(json); + test( json[0] ); + char *ini = obj_saveini(b); // puts(ini); + test( ini[0] ); + char *bin = obj_savebin(b); // puts(bin); + test( bin[0] ); + + obj_push(c); + + test( obj_copy(c,b) ); + test( obj_comp(b,c) == 0 ) || obj_hexdump(b) & obj_hexdump(c); + + test( obj_zero(c) ); + test( obj_comp(c,b) != 0 ) || obj_hexdump(c); + test( obj_loadbin(c, bin) ); + test( obj_comp(c,b) == 0 ) || obj_hexdump(c) & obj_hexdump(b); + + test( obj_zero(c) ); + test( obj_comp(c,b) != 0 ) || obj_hexdump(c); + test( obj_loadini(c, ini) ); + test( obj_comp(c,b) == 0 ) || obj_hexdump(c) & obj_hexdump(b); + + test( obj_zero(c) ); + test( obj_comp(c,b) != 0 ) || obj_hexdump(c); + test( obj_loadjson(c, json) ); + test( obj_comp(c,b) == 0 ) || obj_hexdump(c) & obj_hexdump(b); + + obj_pop(c); + obj_hexdump(c); +} + +// ---------------------------------------------------------------------------- +// components + +bool obj_addcomponent(void *object, unsigned c, void *ptr) { + entity *e = (entity*)object; + e->cflags |= (3ULL << c); + e->c[c & (OBJCOMPONENTS_MAX-1)] = ptr; + return 1; +} +bool obj_hascomponent(void *object, unsigned c) { + entity *e = (entity*)object; + return !!(e->cflags & (3ULL << c)); +} +void* obj_getcomponent(void *object, unsigned c) { + entity *e = (entity*)object; + return e->c[c & (OBJCOMPONENTS_MAX-1)]; +} +bool obj_delcomponent(void *object, unsigned c) { + entity *e = (entity*)object; + e->cflags &= ~(3ULL << c); + e->c[c & (OBJCOMPONENTS_MAX-1)] = NULL; + return 1; +} +bool obj_usecomponent(void *object, unsigned c) { + entity *e = (entity*)object; + e->cflags |= (1ULL << c); + return 1; +} +bool obj_offcomponent(void *object, unsigned c) { + entity *e = (entity*)object; + e->cflags &= ~(1ULL << c); + return 0; +} + +char *entity_save(entity *self) { + char *sav = obj_saveini(self); + return sav; +} + +AUTORUN { + STRUCT(entity, uintptr_t, cflags); + +// struct { OBJHEADER union { struct { uintptr_t objenabled : 32, objflagged : 32; }; uintptr_t cflags; }; void* c[32]; }; + + obj_extend(entity, save); +} + +static +void test_obj_ecs() { + entity *e = obj_new(entity); + puts(obj_save(e)); + + for( int i = 0; i < 32; ++i) test(0 == obj_hascomponent(e, i)); + for( int i = 0; i < 32; ++i) test(1 == obj_addcomponent(e, i, NULL)); + for( int i = 0; i < 32; ++i) test(1 == obj_hascomponent(e, i)); + for( int i = 0; i < 32; ++i) test(1 == obj_delcomponent(e, i)); + for( int i = 0; i < 32; ++i) test(0 == obj_hascomponent(e, i)); +} + +// ---------------------------------------------------------------------------- +// reflection + +void *obj_clone(const void *src) { + obj *ptr = obj_malloc( sizeof(obj) + obj_size(src) + sizeof(array(obj*)) ); + ptr->objheader = ((const obj *)src)->objheader; + obj_loadini(ptr, obj_saveini(src)); + return ptr; +} + +void* obj_merge(void *dst, const void *src) { // @testme + char *bin = obj_savebin(src); + return obj_mergebin(dst, bin); +} + +void* obj_mutate(void **dst, const void *src) { +#if 0 + // mutate a class. ie, convert a given object class into a different one, + // while preserving the original metas, components and references as much as possible. + // @todo iterate per field + + if(!*dst_) return *dst_ = obj_clone(src); + + void *dst = *dst_; + dtor(dst); + + unsigned src_sz = obj_sizeof(src); + unsigned src_id = obj_id(src); + + void *dst_ptr = *((void**)dst - 1); + unsigned payload = (OBJPAYLOAD16(dst_ptr) & 255) | src_id << 8; + FREE( OBJUNBOX(dst_ptr) ); + *((void**)dst - 1) = OBJBOX( STRDUP( OBJUNBOX(*((void**)src - 1)) ), payload); + + void *base = (void*)((void**)dst - 1); + base = REALLOC(base, src_sz + sizeof(void*)); + *dst_ = (char*)base + sizeof(void*); + dst = (char*)base + sizeof(void*); + memcpy(dst, src, src_sz); + + ctor(dst); +#endif + return dst; +} + +void *obj_make(const char *str) { + const char *T; + const char *I = strchr(str, '['); // is_ini + const char *J = strchr(str, '{'); // is_json + if( !I && !J ) return 0; + else if( I && !J ) T = I; + else if( !I && J ) T = J; + else T = I < J ? I : J; + + char name[64] = {0}; + if( sscanf(T+1, T == I ? "%64[^]]" : "%64[^:=]", &name) != 1 ) return 0; + + int has_components = 0; // @todo: support entities too + + unsigned Tid = intern(name); + reflect_init(); + reflect_t *found = map_find(reflects, Tid); + if(!found) return 0; + + obj *ptr = CALLOC(1, found->sz + has_components * sizeof(array(obj*))); + void *ret = (T == I ? obj_mergeini : obj_mergejson)(ptr, str); + OBJTYPES[ found->objtype ] = found->name; + OBJ_CTOR_PTR(ptr,1,found->id,found->sz,found->objtype); + + return ptr; // returns partial construction as well. @todo: just return `ret` for a more strict built/failed policy +} #line 0 #line 1 "engine/split/v4k_ai.c" @@ -357175,9 +358842,6 @@ void v4k_quit(void) { void v4k_init() { do_once { - // abort run if any test suite failed - if( test_errs ) exit(-1); - // install signal handlers ifdef(debug, trap_install()); diff --git a/engine/split/3rd_eval.h b/engine/split/3rd_eval.h new file mode 100644 index 0000000..4cc360f --- /dev/null +++ b/engine/split/3rd_eval.h @@ -0,0 +1,490 @@ +/* A mathematical expression evaluator. + * It uses a recursive descent parser internally. + * Author: Werner Stoop + * This is free and unencumbered software released into the public domain. + * http://unlicense.org/ + */ + +#include +#include +#include /* remember to compile with -lm */ +#include +#include +#include + +/* Special tokens used by the lexer function lex() + * they've been chosen as non-printable characters + * so that printable characters can be used for other + * purposes + */ +#define TOK_END 0 /* end of text */ +#define TOK_INI 1 /* Initial state */ +#define TOK_ID 2 /* identifier */ +#define TOK_NUM 3 /* number */ + +/* Types of errors */ + // 0 /* "no error" */ +#define ERR_MEMORY 1 /* "out of memory" */ +#define ERR_LEXER 2 /* "unknown token" */ +#define ERR_LONGID 3 /* "identifier too long" */ +#define ERR_VALUE 4 /* "value expected" */ +#define ERR_BRACKET 5 /* "missing ')'" */ +#define ERR_FUNC 6 /* "unknown function" */ +#define ERR_ARGS 7 /* "wrong number of arguments" */ +#define ERR_CONST 8 /* "unknown constant" */ + +/* Other definitions */ +#define MAX_ID_LEN 11 /* Max length of an identifier */ +#define OPERATORS "+-*/%(),^" /* Valid operators */ + +#define EVAL_PI 3.141592654 +#define EVAL_E 2.718281828 +#define EVAL_DEG (EVAL_PI/180) + +/* Internal structure for the parser/evaluator */ +struct eval { + + jmp_buf j; /* For error handling */ + + const char *p; /* Position in the text being parsed */ + + double *st; /* Stack */ + int st_size; /* Stack size */ + int sp; /* Stack pointer */ + + /* The current and next tokens identified by the lexer */ + struct { + int type; /* Type of the token */ + double n_val; /* Numeric value of the previous lexed token */ + char s_val[MAX_ID_LEN]; /* String (identifier) value of the previous lexed token */ + } token[2]; + + int cur_tok; /* Current token, either 0 or 1 (see the comments of lex()) */ +}; + +/* Prototypes */ +static double pop(struct eval *ev); +static void push(struct eval *ev, double d); +static int lex(struct eval *ev); + +/* Prototypes for the recursive descent parser */ +static void expr(struct eval *ev); +static void add_expr(struct eval *ev); +static void mul_expr(struct eval *ev); +static void pow_expr(struct eval *ev); +static void uni_expr(struct eval *ev); +static void bra_expr(struct eval *ev); +static void id_expr(struct eval *ev); +static void num_expr(struct eval *ev); + +/* + * Evaluates a mathemeatical expression + */ +double eval(const char *exp/*, int *ep*/) { +int _ep, *ep = &_ep; + struct eval ev; + double ans = 0.0; + + assert(ep != NULL); + + /* Allocate a stack */ + ev.st_size = 10; + ev.st = CALLOC(ev.st_size, sizeof *ev.st); + if(!ev.st) + { + *ep = ERR_MEMORY; + return NAN; //0.0; + } + ev.sp = 0; + + /* Manage errors */ + *ep = setjmp(ev.j); + if(*ep != 0) + { + FREE(ev.st); + return NAN; //0.0; + } + + /* Initialize the lexer */ + ev.token[0].type = TOK_INI; + ev.token[0].s_val[0] = '\0'; + ev.token[1].type = TOK_INI; + ev.token[1].s_val[0] = '\0'; + ev.cur_tok = 0; + + /* Initialize the parser */ + ev.p = exp; + + /* lex once to initialize the lexer */ + if(lex(&ev) != TOK_END) + { + expr(&ev); + ans = pop(&ev); + } + + FREE(ev.st); + return ans; +} + +/* + * Pushes a value onto the stack, increases the stack size if necessary + */ +static void push(struct eval *ev, double d) { + if(ev->sp == ev->st_size) { + /* Resize the stack by 1.5 */ + double *old = ev->st; + int new_size = ev->st_size + (ev->st_size >> 1); + ev->st = REALLOC(ev->st, new_size); + if(!ev->st) { + ev->st = old; + longjmp(ev->j, ERR_MEMORY); + } + + ev->st_size = new_size; + } + + ev->st[ev->sp++] = d; +} + +// Pops a value from the top of the stack +static double pop(struct eval *ev) { + assert(ev->sp > 0); + return ev->st[--ev->sp]; +} + +// stricmp() is common, but not standard, so I provide my own +static int istrcmp(const char *p, const char *q) { + for(; tolower(p[0]) == tolower(q[0]) && p[0]; p++, q++); + return tolower(p[0]) - tolower(q[0]); +} + +/* + * Lexical analyzer function + * + * In order to implement LL(1), struct eval has an array of two token structures, + * and its cur_tok member is used to point to the _current_ token, while the other + * element contains the _next_ token. This implements a 2 element ring buffer where + * the lexer always writes to the _next_ token so that the recursive descent parser can + * _peek_ at the next token. + */ + +static int lex(struct eval *ev) { + int next_tok; + +start: + /* Cycle the tokens */ + next_tok = ev->cur_tok; + ev->cur_tok = ev->cur_tok?0:1; + + while(isspace(ev->p[0])) ev->p++; + + if(!ev->p[0]) { + /* End of the expression */ + ev->token[next_tok].type = TOK_END; + goto end; + } + else if(isdigit(ev->p[0]) || ev->p[0] == '.') { + /* Number */ + char *endp; + ev->token[next_tok].type = TOK_NUM; + ev->token[next_tok].n_val = strtod(ev->p, &endp); + ev->p = endp; + goto end; + } + else if(isalpha(ev->p[0])) { + /* Identifier */ + int i; + for(i = 0; isalnum(ev->p[0]) && i < MAX_ID_LEN - 1; i++, ev->p++) + ev->token[next_tok].s_val[i] = ev->p[0]; + + if(isalpha(ev->p[0])) longjmp(ev->j, ERR_LONGID); + + ev->token[next_tok].s_val[i] = '\0'; + ev->token[next_tok].type = TOK_ID; + goto end; + } + else if(strchr(OPERATORS, ev->p[0])) { + /* Operator */ + ev->token[next_tok].type = ev->p[0]; + ev->p++; + goto end; + } + else /* Unknown token */ + longjmp(ev->j, ERR_LEXER); + +end: + + /* If this was the first call, cycle the tokens again */ + if(ev->token[ev->cur_tok].type == TOK_INI) + goto start; + + return ev->token[ev->cur_tok].type; +} + +#define EVAL_TYPE(e) (e->token[e->cur_tok].type) +#define EVAL_ERROR(c) longjmp(ev->j, (c)) + +// num_expr ::= NUMBER +static void num_expr(struct eval *ev) { + if(EVAL_TYPE(ev) != TOK_NUM) + EVAL_ERROR(ERR_VALUE); + push(ev, ev->token[ev->cur_tok].n_val); + lex(ev); +} + +// expr ::= add_expr +static void expr(struct eval *ev) { + add_expr(ev); +} + +// add_expr ::= mul_expr [('+'|'-') mul_expr] +static void add_expr(struct eval *ev) { + int t; + mul_expr(ev); + while((t =EVAL_TYPE(ev)) == '+' || t == '-') { + double a,b; + lex(ev); + mul_expr(ev); + b = pop(ev); + a = pop(ev); + + if(t == '+') + push(ev, a + b); + else + push(ev, a - b); + } +} + +// mul_expr ::= pow_expr [('*'|'/'|'%') pow_expr] +static void mul_expr(struct eval *ev) { + int t; + pow_expr(ev); + while((t = EVAL_TYPE(ev)) == '*' || t == '/' || t == '%') { + double a,b; + lex(ev); + pow_expr(ev); + b = pop(ev); + a = pop(ev); + + if(t == '*') + push(ev, a * b); + else if(t == '/') + push(ev, a / b); + else + push(ev, fmod(a, b)); + } +} + +// pow_expr ::= uni_expr ['^' pow_expr] +static void pow_expr(struct eval *ev) { + /* Note that exponentiation is right associative: + 2^3^4 is 2^(3^4), not (2^3)^4 */ + uni_expr(ev); + if(EVAL_TYPE(ev) == '^') { + double a,b; + lex(ev); + pow_expr(ev); + b = pop(ev); + a = pop(ev); + push(ev, pow(a,b)); + } +} + +// uni_expr ::= ['+'|'-'] bra_expr +static void uni_expr(struct eval *ev) { + int t = '+'; + if(EVAL_TYPE(ev) == '-' || EVAL_TYPE(ev) == '+') { + t = EVAL_TYPE(ev); + lex(ev); + } + + bra_expr(ev); + + if(t == '-') { + double a = pop(ev); + push(ev, -a); + } +} + +// bra_expr ::= '(' add_expr ')' | id_expr +static void bra_expr(struct eval *ev) { + if(EVAL_TYPE(ev) == '(') { + lex(ev); + add_expr(ev); + if(EVAL_TYPE(ev) != ')') + EVAL_ERROR(ERR_BRACKET); + lex(ev); + } + else + id_expr(ev); +} + +// id_expr ::= ID '(' add_expr [',' add_expr]* ')' | ID | num_expr +static void id_expr(struct eval *ev) { + int nargs = 0; + char id[MAX_ID_LEN]; + if(EVAL_TYPE(ev) != TOK_ID) { + num_expr(ev); + } else { + strcpy(id, ev->token[ev->cur_tok].s_val); + lex(ev); + if(EVAL_TYPE(ev) != '(') { + /**/ if(!istrcmp(id, "true")) push(ev, 1.0); + else if(!istrcmp(id, "false")) push(ev, 0.0); + else if(!istrcmp(id, "on")) push(ev, 1.0); + else if(!istrcmp(id, "off")) push(ev, 0.0); + // pi - 3.141592654 + else if(!istrcmp(id, "pi")) + push(ev, EVAL_PI); + // e - base of natural logarithms, 2.718281828 + else if(!istrcmp(id, "e")) + push(ev, EVAL_E); + // deg - deg2rad, allows to degree conversion `sin(90*deg) = 1` + else if(!istrcmp(id, "deg")) + push(ev, EVAL_DEG); + else + EVAL_ERROR(ERR_CONST); + } else { + lex(ev); + + while(EVAL_TYPE(ev) != ')') { + add_expr(ev); + nargs++; + if(EVAL_TYPE(ev) == ')') break; + + if(EVAL_TYPE(ev) != ',') + EVAL_ERROR(ERR_BRACKET); + lex(ev); + } + lex(ev); + + // abs(x) - absolute value of x + if(!istrcmp(id, "abs")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, fabs(pop(ev))); + } + // ceil(x) - smallest integer greater than x + else if(!istrcmp(id, "ceil")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, ceil(pop(ev))); + } + // floor(x) - largest integer smaller than x + else if(!istrcmp(id, "floor")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, floor(pop(ev))); + } + // sin(x) - sine of x, in radians + else if(!istrcmp(id, "sin")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, sin(pop(ev))); + } + // asin(x) - arcsine of x, in radians + else if(!istrcmp(id, "asin")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, asin(pop(ev))); + } + // cos(x) - cosine of x, in radians + else if(!istrcmp(id, "cos")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, cos(pop(ev))); + } + // acos(x) - arccosine of x, in radians + else if(!istrcmp(id, "acos")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, acos(pop(ev))); + } + // tan(x) - tangent of x, in radians + else if(!istrcmp(id, "tan")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, tan(pop(ev))); + } + // atan(x) - arctangent of x, in radians + else if(!istrcmp(id, "atan")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, atan(pop(ev))); + } + // atan(y,x) - arctangent of y/x, in radians. + else if(!istrcmp(id, "atan2")) { + double a, b; + if(nargs != 2) EVAL_ERROR(ERR_ARGS); + b = pop(ev); + a = pop(ev); + push(ev, atan2(a,b)); + } + // sinh(x) - hyperbolic sine of x, in radians + else if(!istrcmp(id, "sinh")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, sinh(pop(ev))); + } + // cosh(x) - hyperbolic cosine of x, in radians + else if(!istrcmp(id, "cosh")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, cosh(pop(ev))); + } + // tanh(x) - hyperbolic tangent of x, in radians + else if(!istrcmp(id, "tanh")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, tanh(pop(ev))); + } + // log(x) - natural logarithm of x + else if(!istrcmp(id, "log")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, log(pop(ev))); + } + // log10(x) - logarithm of x, base-10 + else if(!istrcmp(id, "log10")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, log10(pop(ev))); + } + // exp(x) - computes e^x + else if(!istrcmp(id, "exp")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, exp(pop(ev))); + } + // sqrt(x) - square root of x + else if(!istrcmp(id, "sqrt")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, sqrt(pop(ev))); + } + // rad(x) - converts x from degrees to radians + else if(!istrcmp(id, "rad")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, pop(ev)*EVAL_PI/180); + } + // deg(x) - converts x from radians to degrees + else if(!istrcmp(id, "deg")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, pop(ev)*180/EVAL_PI); + } + // pow(x,y) - computes x^y + else if(!istrcmp(id, "pow")) { + double a, b; + if(nargs != 2) EVAL_ERROR(ERR_ARGS); + b = pop(ev); + a = pop(ev); + push(ev, pow(a,b)); + } + // hypot(x,y) - computes sqrt(x*x + y*y) + else if(!istrcmp(id, "hypot")) { + double a, b; + if(nargs != 2) EVAL_ERROR(ERR_ARGS); + b = pop(ev); + a = pop(ev); + push(ev, sqrt(a*a + b*b)); + } + else + EVAL_ERROR(ERR_FUNC); + } + } +} + +// + +#ifdef EVALDEMO +#include +int main() { + assert( eval("1+1") == 2 ); // common path + assert( eval("1+") != eval("1+") ); // check that errors return NAN + assert(~puts("Ok") ); +} +#endif diff --git a/engine/split/3rd_nuklear.h b/engine/split/3rd_nuklear.h index f2fec0d..6dd3e9c 100644 --- a/engine/split/3rd_nuklear.h +++ b/engine/split/3rd_nuklear.h @@ -8020,7 +8020,7 @@ nk_utf_decode(const char *c, nk_rune *u, int clen) *u = NK_UTF_INVALID; udecoded = nk_utf_decode_byte(c[0], &len); - if (!NK_BETWEEN(len, 1, NK_UTF_SIZE)) + if (!NK_BETWEEN(len, 1, NK_UTF_SIZE+1)) //< @r-lyeh: add +1 for len<=4 return 1; for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { @@ -20155,7 +20155,12 @@ window->is_window_resizing |= layout->flags & NK_WINDOW_SCALE_TOP ? NK_WINDOW_SC } } - ctx->style.cursor_active = ctx->style.cursors[NK_CURSOR_RESIZE_TOP_RIGHT_DOWN_LEFT]; + int icon = //< @r-lyeh + ((layout->flags & NK_WINDOW_SCALE_TOP) && !(layout->flags & NK_WINDOW_SCALE_LEFT)) + || + ((layout->flags & NK_WINDOW_SCALE_LEFT) && !(layout->flags & NK_WINDOW_SCALE_TOP)) + ? NK_CURSOR_RESIZE_TOP_LEFT_DOWN_RIGHT : NK_CURSOR_RESIZE_TOP_RIGHT_DOWN_LEFT; + ctx->style.cursor_active = ctx->style.cursors[icon]; //< @r-lyeh in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.x = scaler.x + scaler.w/2.0f; in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.y = scaler.y + scaler.h/2.0f; } diff --git a/engine/split/v4k.x.inl b/engine/split/v4k.x.inl index 121c4b4..4afe7fb 100644 --- a/engine/split/v4k.x.inl +++ b/engine/split/v4k.x.inl @@ -169,6 +169,8 @@ errno_t fopen_s( {{FILE:3rd_xml.h}} #undef g {{FILE:3rd_polychop.h}} +#define expr expr2 // 3rd_lua.h +{{FILE:3rd_eval.h}} // #define SQLITE_OMIT_LOAD_EXTENSION // #define SQLITE_CORE 1 // #define SQLITE_DEBUG 1 diff --git a/engine/split/v4k_config.h b/engine/split/v4k_config.h index 0eef31f..d28c26a 100644 --- a/engine/split/v4k_config.h +++ b/engine/split/v4k_config.h @@ -169,7 +169,7 @@ #define conc4t(a,b) a##b ///- #define macro(name) concat(name, __LINE__) -#define unique(name) concat(concat(name, L##__LINE__), __COUNTER__) +#define unique(name) concat(concat(concat(name,concat(_L,__LINE__)),_),__COUNTER__) #define defer(begin,end) for(int macro(i) = ((begin), 0); !macro(i); macro(i) = ((end), 1)) #define scope(end) defer((void)0, end) #define benchmark for(double macro(i) = 1, macro(t) = (time_ss(),-time_ss()); macro(i); macro(t)+=time_ss(), macro(i)=0, printf("%.4fs %2.f%% (" FILELINE ")\n", macro(t), macro(t)*100/0.0166667 )) @@ -194,8 +194,8 @@ #define ASSERT(expr, ...) (void)0 #define ASSERT_ONCE(expr, ...) (void)0 #else -#define ASSERT(expr, ...) do { int fool_msvc[] = {0,}; if(!(expr)) { fool_msvc[0]++; breakpoint(va("!Expression failed: " #expr " " FILELINE "\n" __VA_ARGS__)); } } while(0) -#define ASSERT_ONCE(expr, ...) do { int fool_msvc[] = {0,}; if(!(expr)) { fool_msvc[0]++; static int seen = 0; if(!seen) seen = 1, breakpoint(va("!Expression failed: " #expr " " FILELINE "\n" __VA_ARGS__)); } } while(0) +#define ASSERT(expr, ...) do { int fool_msvc[] = {0,}; if(!(expr)) { fool_msvc[0]++; alert(va("!Expression failed: " #expr " " FILELINE "\n" __VA_ARGS__)), breakpoint(); } } while(0) +#define ASSERT_ONCE(expr, ...) do { int fool_msvc[] = {0,}; if(!(expr)) { fool_msvc[0]++; static int seen = 0; if(!seen) seen = 1, alert(va("!Expression failed: " #expr " " FILELINE "\n" __VA_ARGS__)), breakpoint(); } } while(0) #endif #define STATIC_ASSERT(EXPR) typedef struct { unsigned macro(static_assert_on_L) : !!(EXPR); } unique(static_assert_on_L) @@ -274,6 +274,7 @@ // note: based on code by Joe Lowe (public domain). // note: XIU for C initializers, XCU for C++ initializers, XTU for C deinitializers +#define AUTORUN AUTORUN_( unique(fn) ) #ifdef __cplusplus #define AUTORUN_(fn) \ static void fn(void); \ @@ -297,8 +298,6 @@ static void fn(void) #endif -#define AUTORUN AUTORUN_( concat(concat(concat(fn_L,__LINE__),_),__COUNTER__) ) - #if 0 // autorun demo void byebye(void) { puts("seen after main()"); } AUTORUN { puts("seen before main()"); } @@ -315,7 +314,7 @@ AUTORUN { puts("seen before main() too"); atexit( byebye ); } // ----------------------------------------------------------------------------- // visibility -// win32 users would need to -DAPI=IMPORT/EXPORT as needed when using/building V4K as DLL. +// win32 users would need to -DAPI=EXPORT/IMPORT as needed when building/using V4K as DLL. #define IMPORT ifdef(win32, ifdef(gcc, __attribute__ ((dllimport)), __declspec(dllimport))) #define EXPORT ifdef(win32, ifdef(gcc, __attribute__ ((dllexport)), __declspec(dllexport))) diff --git a/engine/split/v4k_ds.h b/engine/split/v4k_ds.h index 2fb5bfc..3327ccc 100644 --- a/engine/split/v4k_ds.h +++ b/engine/split/v4k_ds.h @@ -74,7 +74,7 @@ static __thread unsigned array_n_; #define array_vlen_(t) ( vlen(t) - 0 ) #define array_realloc_(t,n) ( (t) = array_cast(t) vrealloc((t), ((n)+0) * sizeof(0[t])) ) #define array_free(t) array_clear(t) -#else // new: with reserve support (bugs?) +#else // new: with reserve support (@todo: check for bugs?) #define array_reserve(t, n) ( array_realloc_((t),(n)), array_clear(t) ) #define array_clear(t) ( array_realloc_((t),0) ) // -1 #define array_vlen_(t) ( vlen(t) - sizeof(0[t]) ) // -1 @@ -121,7 +121,7 @@ static __thread unsigned array_n_; memcpy( (t), src, array_count(src) * sizeof(0[t])); \ } while(0) -#define array_erase_fast(t, i) do { /*may alter ordering*/ \ +#define array_erase_fast(t, i) do { /*alters ordering*/ \ memcpy( &(t)[i], &(t)[array_count(t) - 1], sizeof(0[t])); \ array_pop(t); \ } while(0) diff --git a/engine/split/v4k_font.c b/engine/split/v4k_font.c index 7ae1309..2e97ded 100644 --- a/engine/split/v4k_font.c +++ b/engine/split/v4k_font.c @@ -1634,7 +1634,7 @@ void font_color(const char *tag, uint32_t color) { if( f->initialized ) { glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_1D, f->texture_colors); - glTexSubImage1D(GL_TEXTURE_1D, 0, 0, FONT_MAX_COLORS, GL_BGRA, GL_UNSIGNED_BYTE, font_palette); + glTexSubImage1D(GL_TEXTURE_1D, 0, 0, FONT_MAX_COLORS, GL_RGBA, GL_UNSIGNED_BYTE, font_palette); } } } @@ -1832,7 +1832,7 @@ void font_face_from_mem(const char *tag, const void *ttf_bufferv, unsigned ttf_l glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // last chance to inspect the font atlases - if( flag("--debug-font-atlas") ) + if( flag("--font-debug") ) stbi_write_png(va("debug_font_atlas%d.png", index), f->width, f->height, 1, bitmap, 0); FREE(bitmap); @@ -1880,7 +1880,7 @@ void font_face_from_mem(const char *tag, const void *ttf_bufferv, unsigned ttf_l glGenTextures(1, &f->texture_colors); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_1D, f->texture_colors); - glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, FONT_MAX_COLORS, 0, GL_BGRA, GL_UNSIGNED_BYTE, font_palette); + glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, FONT_MAX_COLORS, 0, GL_RGBA, GL_UNSIGNED_BYTE, font_palette); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT); diff --git a/engine/split/v4k_main.c b/engine/split/v4k_main.c index 78f987d..a543939 100644 --- a/engine/split/v4k_main.c +++ b/engine/split/v4k_main.c @@ -60,9 +60,6 @@ void v4k_quit(void) { void v4k_init() { do_once { - // abort run if any test suite failed - if( test_errs ) exit(-1); - // install signal handlers ifdef(debug, trap_install()); diff --git a/engine/split/v4k_math.c b/engine/split/v4k_math.c index 41714c7..52f2677 100644 --- a/engine/split/v4k_math.c +++ b/engine/split/v4k_math.c @@ -112,9 +112,6 @@ float ease_inout_bounce(float t) { return t < 0.5f ? 0.5f*ease_in_bounce(t*2) : float ease_inout_perlin(float t) { float t3=t*t*t,t4=t3*t,t5=t4*t; return 6*t5-15*t4+10*t3; } -float ease_ping_pong(float t, float(*fn1)(float), float(*fn2)(float)) { return t < 0.5 ? fn1(t*2) : fn2(1-(t-0.5)*2); } -float ease_pong_ping(float t, float(*fn1)(float), float(*fn2)(float)) { return 1 - ease_ping_pong(t,fn1,fn2); } - float ease(float t01, unsigned mode) { typedef float (*easing)(float); easing modes[] = { @@ -159,6 +156,10 @@ float ease(float t01, unsigned mode) { return modes[clampi(mode, 0, countof(modes))](clampf(t01,0,1)); } +float ease_pong(float t, unsigned fn) { return 1 - ease(t, fn); } +float ease_ping_pong(float t, unsigned fn1, unsigned fn2) { return t < 0.5 ? ease(t*2,fn1) : ease(1-(t-0.5)*2,fn2); } +float ease_pong_ping(float t, unsigned fn1, unsigned fn2) { return 1 - ease_ping_pong(t,fn1,fn2); } + // ---------------------------------------------------------------------------- float deg (float radians) { return radians / C_PI * 180.0f; } @@ -953,3 +954,11 @@ void printq( quat q ) { print_(&q.x,4,1); } void print33( float *m ) { print_(m,3,3); } void print34( float *m ) { print_(m,3,4); } void print44( float *m ) { print_(m,4,4); } + +// ----------- + +AUTORUN { + STRUCT( vec3, float, x ); + STRUCT( vec3, float, y ); + STRUCT( vec3, float, z, "Up" ); +} diff --git a/engine/split/v4k_math.h b/engine/split/v4k_math.h index 23dcd9b..6ab1d80 100644 --- a/engine/split/v4k_math.h +++ b/engine/split/v4k_math.h @@ -5,9 +5,9 @@ // Credits: @ands+@krig+@vurtun (PD), @datenwolf (WTFPL2), @evanw+@barerose (CC0), @sgorsten (Unlicense). #define C_EPSILON (1e-6) -#define C_PI (3.141592654f) // (3.14159265358979323846f) -#define TO_RAD (C_PI/180.f) -#define TO_DEG (180.f/C_PI) +#define C_PI (3.14159265358979323846f) // (3.141592654f) +#define TO_RAD (C_PI/180) +#define TO_DEG (180/C_PI) // ---------------------------------------------------------------------------- @@ -39,7 +39,6 @@ API void randset(uint64_t state); API uint64_t rand64(void); API double randf(void); // [0, 1) interval API int randi(int mini, int maxi); // [mini, maxi) interval -//API double rng(void); // [0..1) Lehmer RNG "minimal standard" // ---------------------------------------------------------------------------- @@ -105,10 +104,10 @@ enum EASE_FLAGS { EASE_OUT = 0, }; -API float ease(float t01, unsigned mode); // 0=linear,1=out_sine...31=inout_perlin - -API float ease_ping_pong(float t, float(*fn1)(float), float(*fn2)(float)); -API float ease_pong_ping(float t, float(*fn1)(float), float(*fn2)(float)); +API float ease(float t01, unsigned fn); // / 0-to-1 +API float ease_pong(float t01, unsigned fn); // \ 1-to-0 +API float ease_ping_pong(float t, unsigned fn1, unsigned fn2); // /\ 0-to-1-to-0 +API float ease_pong_ping(float t, unsigned fn1, unsigned fn2); // \/ 1-to-0-to-1 // ---------------------------------------------------------------------------- diff --git a/engine/split/v4k_obj.c b/engine/split/v4k_obj.c index 9cf42d8..18db346 100644 --- a/engine/split/v4k_obj.c +++ b/engine/split/v4k_obj.c @@ -1,44 +1,801 @@ -// ----------------------------------------------------------------------------- -// semantic versioning in a single byte (octal) +// C objects framework // - rlyeh, public domain. - // -// - single octal byte that represents semantic versioning (major.minor.patch). -// - allowed range [0000..0377] ( <-> [0..255] decimal ) -// - comparison checks only major.minor tuple as per convention. -int semver( int major, int minor, int patch ) { - return SEMVER(major, minor, patch); +// --- implement new vtables + +obj_vtable(ctor, void, {} ); +obj_vtable(dtor, void, {} ); + +obj_vtable_null(save, char* ); +obj_vtable_null(load, bool ); +obj_vtable_null(test, int ); + +obj_vtable_null(init, int ); +obj_vtable_null(quit, int ); +obj_vtable_null(tick, int ); +obj_vtable_null(draw, int ); + +obj_vtable_null(lerp, int ); +obj_vtable_null(edit, int ); // OSC cmds: argc,argv "undo","redo","cut","copy","paste","edit","view","menu" + +// ---------------------------------------------------------------------------- + +const char *OBJTYPES[256] = { 0 }; // = { REPEAT256("") }; + +// ---------------------------------------------------------------------------- +// heap/stack ctor/dtor + +void *obj_malloc(unsigned sz) { + //sz = sizeof(obj) + sz + sizeof(array(obj*))); // useful? + obj *ptr = CALLOC(1, sz); + OBJ_CTOR_HDR(ptr,1,intern("obj"),sz,OBJTYPE_obj); + return ptr; } -int semvercmp( int v1, int v2 ) { - return SEMVERCMP(v1, v2); +void *obj_free(void *o) { + if( !((obj*)o)->objrefs ) { + obj_dtor(o); + //obj_zero(o); + if( ((obj*)o)->objheap ) { + FREE(o); + } + return 0; +} + return o; // cannot destroy: object is still referenced } -#if 0 -AUTORUN { - for( int i= 0; i <= 255; ++i) printf(SEMVERFMT ",", i); - puts(""); +// ---------------------------------------------------------------------------- +// core - printf(SEMVERFMT "\n", semver(3,7,7)); - printf(SEMVERFMT "\n", semver(2,7,7)); - printf(SEMVERFMT "\n", semver(1,7,7)); - printf(SEMVERFMT "\n", semver(0,7,7)); - - printf(SEMVERFMT "\n", semver(3,7,1)); - printf(SEMVERFMT "\n", semver(2,5,3)); - printf(SEMVERFMT "\n", semver(1,3,5)); - printf(SEMVERFMT "\n", semver(0,1,7)); - - assert( semvercmp( 0357, 0300 ) > 0 ); - assert( semvercmp( 0277, 0300 ) < 0 ); - assert( semvercmp( 0277, 0200 ) > 0 ); - assert( semvercmp( 0277, 0100 ) < 0 ); - assert( semvercmp( 0076, 0070 ) == 0 ); - assert( semvercmp( 0076, 0077 ) == 0 ); - assert( semvercmp( 0176, 0170 ) == 0 ); - assert( semvercmp( 0176, 0177 ) == 0 ); - assert( semvercmp( 0276, 0270 ) == 0 ); - assert( semvercmp( 0276, 0277 ) == 0 ); - assert( semvercmp( 0376, 0370 ) == 0 ); - assert( semvercmp( 0376, 0377 ) == 0 ); +uintptr_t obj_header(const void *o) { + return ((obj*)o)->objheader; } +uintptr_t obj_id(const void *o) { + return ((obj*)o)->objid; +} +unsigned obj_typeid(const void *o) { + return ((obj*)o)->objtype; +} +const char *obj_type(const void *o) { + return OBJTYPES[ (((obj*)o)->objtype) ]; +} +const char *obj_name(const void *o) { + return quark(((obj*)o)->objnameid); +} +int obj_sizeof(const void *o) { + return (int)( ((const obj*)o)->objsizew << OBJ_MIN_PRAGMAPACK_BITS ); +} +int obj_size(const void *o) { // size of all members together in struct. may include padding bytes. + static int obj_sizes[256] = {0}; + unsigned objtypeid = ((obj*)o)->objtype; + if( objtypeid > 1 && !obj_sizes[objtypeid] ) { // check reflection for a more accurate objsize (without padding bits) + reflect_init(); + array(reflect_t) *found = map_find(members, intern(obj_type(o))); + if(!found) + obj_sizes[objtypeid] = obj_sizeof(o) - sizeof(obj); // @fixme: -= sizeof(entity); + else + for each_array_ptr(*found, reflect_t, it) + obj_sizes[objtypeid] += it->bytes; + } + return obj_sizes[objtypeid]; +} +char *obj_data(void *o) { // pointer to the first member in struct + return (char*)o + sizeof(obj); +} +const char *obj_datac(const void *o) { // const pointer to the first struct member + return (const char*)o + sizeof(obj); +} +void* obj_payload(const void *o) { // pointer right after last member in struct + return (char*)o + (((obj*)o)->objsizew<objrefs; + ++o->objrefs; + assert( num < o->objrefs && "Object referenced too many times"); + //++global_ref_count; + return o; +} +void *obj_unref(void *oo) { + obj* o = (obj*)oo; + if( o->objrefs ) --o->objrefs; + if( o->objrefs ) return o; + obj_free(o); + //--global_ref_count; + return 0; +} + +// ---------------------------------------------------------------------------- +// scene tree + +array(obj*)* obj_children(const void *o) { + array(obj*) *c = obj_payload(o); + if(!(*c)) array_push((*c), NULL); // default parenting: none. @todo: optimize & move this at construction time + return c; +} +obj* obj_parent(const void *o) { + array(obj*) *c = obj_children(o); + return (*c) ? 0[*c] : NULL; +} +obj* obj_root(const void *o) { + while( obj_parent(o) ) o = obj_parent(o); + return (obj*)o; +} +array(obj*)* obj_siblings(const void *o) { + return obj_children(obj_parent(o)); +} + +static +obj* obj_reparent(obj *o, const void *p) { + array(obj*) *c = obj_children(o); + 0[*c] = (void*)p; + return o; +} + +obj* obj_detach(void *c) { + obj *p = obj_parent(c); + if( p ) { + uintptr_t id = obj_id(c); + + array(obj*) *oo = obj_children(p); + for( int i = 1, end = array_count(*oo); i < end; ++i) { + obj *v = (*oo)[i]; + { + if( obj_id(v) == id ) { + obj_reparent(c, 0); + return c; + } + } + } + } + return 0; +} +obj* obj_attach(void *o, void *c) { + // reattach + obj_detach(c); + obj_reparent(c, o); + // insert into children + array(obj*) *p = obj_children(o); + array_push(*p, c); + return o; +} + +int obj_dumptree(const void *o) { + static int tabs = 0; + printf("%*s" "+- %s\n", tabs++, "", obj_name(o)); + for each_objchild(o, obj, v) { + obj_dumptree(v); + } + --tabs; + return 0; +} + +static +void test_obj_scene() { + obj *r = obj_new_ext(obj, "root"); // root + obj *c1 = obj_new_ext(obj, "child1"); // child1 + obj *c2 = obj_new_ext(obj, "child2"); // child2 + obj *gc1 = obj_new_ext(obj, "grandchild1"); // grandchild1 + obj *gc2 = obj_new_ext(obj, "grandchild2"); // grandchild2 + obj *gc3 = obj_new_ext(obj, "grandchild3"); // grandchild3 + + test( !obj_parent(r) ); + test( !obj_parent(c1) ); + test( !obj_parent(c2) ); + test( !obj_parent(gc1) ); + test( !obj_parent(gc2) ); + test( !obj_parent(gc3) ); + test( obj_root(r) == r ); + test( obj_root(c1) == c1 ); + test( obj_root(c2) == c2 ); + test( obj_root(gc1) == gc1 ); + test( obj_root(gc2) == gc2 ); + test( obj_root(gc3) == gc3 ); + + // r + obj_attach(r, c1); // +- c1 + obj_attach(c1, gc1); // +- gc1 + obj_attach(r, c2); // +- c2 + obj_attach(c2, gc2); // +- gc2 + obj_attach(c2, gc3); // +- gc3 + + // obj_dumptree(r); + // puts("---"); + + test( obj_parent(r) == 0 ); + test( obj_parent(c1) == r ); + test( obj_parent(c2) == r ); + test( obj_parent(gc1) == c1 ); + test( obj_parent(gc2) == c2 ); + test( obj_parent(gc3) == c2 ); + + test( obj_root(r) == r ); + test( obj_root(c1) == r ); + test( obj_root(c2) == r ); + test( obj_root(gc1) == r ); + test( obj_root(gc2) == r ); + test( obj_root(gc3) == r ); + + for each_objchild(r, obj, o) test( o == c1 || o == c2 ); + for each_objchild(c1, obj, o) test( o == gc1 ); + for each_objchild(c2, obj, o) test( o == gc2 || o == gc3 ); + + obj_detach(c1); + test( !obj_parent(c1) ); + for each_objchild(r, obj, o) test( o != c1 ); + for each_objchild(c1, obj, o) test( o == gc1 ); + + obj_detach(c2); + test( !obj_parent(c2) ); + for each_objchild(r, obj, o) test( o != c2 ); + for each_objchild(c2, obj, o) test( o == gc2 || o == gc3 ); +} + +// ---------------------------------------------------------------------------- +// metadata + +static map(int,int) oms; +static thread_mutex_t *oms_lock; +const char *obj_metaset(const void *o, const char *key, const char *value) { + do_threadlock(oms_lock) { + if(!oms) map_init_int(oms); + int *q = map_find_or_add(oms, intern(va("%llu-%s",obj_id((obj*)o),key)), 0); + if(!*q && !value[0]) {} else *q = intern(value); + return quark(*q); + } + return 0; // unreachable +} +const char* obj_metaget(const void *o, const char *key) { + do_threadlock(oms_lock) { + if(!oms) map_init_int(oms); + int *q = map_find_or_add(oms, intern(va("%llu-%s",obj_id((obj*)o),key)), 0); + return quark(*q); + } + return 0; // unreachable +} + +static +void test_obj_metadatas( void *o1 ) { + obj *o = (obj *)o1; + test( !strcmp("", obj_metaget(o, "has_passed_test")) ); + test( !strcmp("yes", obj_metaset(o, "has_passed_test", "yes")) ); + test( !strcmp("yes", obj_metaget(o, "has_passed_test")) ); +} + +// ---------------------------------------------------------------------------- +// stl + +void* obj_swap(void *dst, void *src) { // @testme + int len = obj_size(dst); + char *buffer = ALLOCA(len); + memcpy(buffer, obj_datac(dst), len); + memcpy(obj_data(dst), obj_datac(src), len); + memcpy(obj_data(src), buffer, len); + return dst; +} + +void* obj_copy_fast(void *dst, const void *src) { + // note: prefer obj_copy() as it should handle pointers and guids as well + return memcpy(obj_data(dst), obj_datac(src), obj_size(dst)); +} +void* obj_copy(void *dst, const void *src) { // @testme + // @todo: use obj_copy_fast() silently if the object does not contain any pointers/guids + return obj_loadini(dst, obj_saveini(src)); + // return obj_load(dst, obj_save(src)); + // return obj_loadbin(dst, obj_savebin(src)); + // return obj_loadini(dst, obj_saveini(src)); + // return obj_loadjson(dst, obj_savejson(src)); + // return obj_loadmpack(dst, obj_savempack(src)); +} + +int obj_comp_fast(const void *a, const void *b) { + // note: prefer obj_comp() as it should handle pointers and guids as well + return memcmp(obj_datac(a), obj_datac(b), obj_size(a)); +} +int obj_comp(const void *a, const void *b) { + // @todo: use obj_comp_fast() silently if the object does not contain any pointers/guids + return strcmp(obj_saveini(a),obj_saveini(b)); +} +int obj_lesser(const void *a, const void *b) { + return obj_comp(a,b) < 0; +} +int obj_greater(const void *a, const void *b) { + return obj_comp(a,b) > 0; +} +int obj_equal(const void *a, const void *b) { + return obj_comp(a,b) == 0; +} + +uint64_t obj_hash(const void *o) { + return hash_bin(obj_datac(o), obj_size(o)); +} + +static +void test_obj_similarity(void *o1, void *o2) { + obj *b = (obj*)o1; + obj *c = (obj*)o2; + test( 0 == strcmp(obj_name(b),obj_name(c)) ); + test( 0 == strcmp(obj_type(b),obj_type(c)) ); +} +static +void test_obj_equality(void *o1, void *o2) { + obj *b = (obj*)o1; + obj *c = (obj*)o2; + test_obj_similarity(b, c); + test( obj_size(b) == obj_size(c) ); + test( obj_hash(b) == obj_hash(c) ); + test( 0 == obj_comp(b,c) ); + test( obj_equal(b,c) ); + test( !obj_lesser(b,c) ); + test( !obj_greater(b,c) ); +} +static +void test_obj_exact(void *o1, void *o2) { + obj *b = (obj*)o1; + obj *c = (obj*)o2; + test_obj_equality(b, c); + test( obj_header(b) == obj_header(c) ); + test( 0 == memcmp(b, c, obj_sizeof(b)) ); +} + +// ---------------------------------------------------------------------------- +// debug + +bool obj_hexdump(const void *oo) { + const obj *o = (const obj *)oo; + int header = 1 * sizeof(obj); + printf("; name[%s] type[%s] id[%d..%d] unused[%08x] sizeof[%02d] %llx\n", + obj_name(o), obj_type(o), + (int)o->objid>>16, (int)o->objid&0xffff, (int)o->objunused, + obj_sizeof(o), o->objheader); + return hexdump(obj_datac(o) - header, obj_size(o) + header), 1; +} +int obj_print(const void *o) { + char *sav = obj_saveini(o); // obj_savejson(o) + return puts(sav); +} +static char *obj_tempname = 0; +static FILE *obj_filelog = 0; +int (obj_printf)(const void *o, const char *text) { + if( !obj_tempname ) { + obj_tempname = stringf("%s.log", app_name()); + unlink(obj_tempname); + obj_filelog = fopen(obj_tempname, "w+b"); + if( obj_filelog ) fseek(obj_filelog, 0L, SEEK_SET); + } + int rc = 0; + for( char *end; (end = strchr(text, '\n')) != NULL; ) { + rc |= fprintf(obj_filelog, "[%p] %.*s\n", o, (int)(end - text), text ); + text = end + 1; + } + if( text[0] ) rc |= fprintf(obj_filelog, "[%p] %s\n", o, text); + return rc; +} +int obj_console(const void *o) { // obj_output() ? + if( obj_filelog ) fflush(obj_filelog); + return obj_tempname && !system(va(ifdef(win32,"type \"%s\" | find \"[%p]\"", "cat %s | grep \"[%p]\""), obj_tempname, o)); +} + +static +void test_obj_console(void *o1) { + obj *o = (obj *)o1; + + obj_printf(o, "this is [%s], line 1\n", obj_name(o)); + obj_printf(NULL, "this line does not belong to any object\n"); + obj_printf(o, "this is [%s], line 2\n", obj_name(o)); + obj_console(o); +} + +// ---------------------------------------------------------------------------- +// serialization + +const char *p2s(const char *type, void *p) { + // @todo: p2s(int interned_type, void *p) + /**/ if( !strcmp(type, "int") ) return itoa1(*(int*)p); + else if( !strcmp(type, "unsigned") ) return itoa1(*(unsigned*)p); + else if( !strcmp(type, "float") ) return ftoa1(*(float*)p); + else if( !strcmp(type, "double") ) return ftoa1(*(double*)p); + else if( !strcmp(type, "uintptr_t") ) return va("%08llx", *(uintptr_t*)p); + else if( !strcmp(type, "vec2i") ) return itoa2(*(vec2i*)p); + else if( !strcmp(type, "vec3i") ) return itoa3(*(vec3i*)p); + else if( !strcmp(type, "vec2") ) return ftoa2(*(vec2*)p); + else if( !strcmp(type, "vec3") ) return ftoa3(*(vec3*)p); + else if( !strcmp(type, "vec4") ) return ftoa4(*(vec4*)p); + else if( !strcmp(type, "char*") || !strcmp(type, "string") ) return va("%s", *(char**)p); + // @todo: if strchr('*') assume obj, if reflected save guid: obj_id(); + return tty_color(YELLOW), printf("p2s: cannot serialize `%s` type\n", type), tty_color(0), ""; +} +bool s2p(void *P, const char *type, const char *str) { + int i; unsigned u; float f; double g; char *s = 0; uintptr_t p; + vec2 v2; vec3 v3; vec4 v4; vec2i v2i; vec3i v3i; + /**/ if( !strcmp(type, "int") ) return !!memcpy(P, (i = atoi1(str), &i), sizeof(i)); + else if( !strcmp(type, "unsigned") ) return !!memcpy(P, (u = atoi1(str), &u), sizeof(u)); + else if( !strcmp(type, "vec2i") ) return !!memcpy(P, (v2i = atoi2(str), &v2i), sizeof(v2i)); + else if( !strcmp(type, "vec3i") ) return !!memcpy(P, (v3i = atoi3(str), &v3i), sizeof(v3i)); + else if( !strcmp(type, "float") ) return !!memcpy(P, (f = atof1(str), &f), sizeof(f)); + else if( !strcmp(type, "double") ) return !!memcpy(P, (g = atof1(str), &g), sizeof(g)); + else if( !strcmp(type, "vec2") ) return !!memcpy(P, (v2 = atof2(str), &v2), sizeof(v2)); + else if( !strcmp(type, "vec3") ) return !!memcpy(P, (v3 = atof3(str), &v3), sizeof(v3)); + else if( !strcmp(type, "vec4") ) return !!memcpy(P, (v4 = atof4(str), &v4), sizeof(v4)); + else if( !strcmp(type, "uintptr_t") ) return !!memcpy(P, (p = strtol(str, NULL, 16), &p), sizeof(p)); + else if( !strcmp(type, "char*") || !strcmp(type, "string") ) { + char substring[128] = {0}; + sscanf(str, "%[^\r\n]", substring); + + strcatf(&s, "%s", substring); + + *(uintptr_t*)(P) = (uintptr_t)s; + return 1; + } + // @todo: if strchr('*') assume obj, if reflected load guid: obj_id(); + return tty_color(YELLOW), printf("s2p: cannot deserialize `%s` type\n", type), tty_color(0), 0; +} + +char *obj_saveini(const void *o) { // @testme + char *out = 0; + const char *T = obj_type(o); + strcatf(&out, "[%s] ; v100\n", T); + for each_member(T,R) { + const char *sav = p2s(R->type,(char*)(o)+R->sz); + if(!sav) return FREE(out), NULL; + strcatf(&out,"%s.%s=%s\n", R->type,R->name,sav ); + } + char *cpy = va("%s", out); + FREE(out); + return cpy; +} +obj *obj_mergeini(void *o, const char *ini) { // @testme + const char *sqr = strchr(ini, '['); + if( !sqr ) return 0; + ini = sqr+1; + + char T[64] = {0}; + if( sscanf(ini, "%64[^]]", &T) != 1 ) return 0; // @todo: parse version as well + ini += strlen(T); + + for each_member(T,R) { + char *lookup = va("\n%s.%s=", R->type,R->name), *found = 0; + + // type needed? /* + if(!found) { found = strstr(ini, lookup); if (found) found += strlen(lookup); } + if(!found) { *lookup = '\r'; } + if(!found) { found = strstr(ini, lookup); if (found) found += strlen(lookup); } + // */ + + if(!found) lookup = va("\n%s=", R->name); + + if(!found) { found = strstr(ini, lookup); if (found) found += strlen(lookup); } + if(!found) { *lookup = '\r'; } + if(!found) { found = strstr(ini, lookup); if (found) found += strlen(lookup); } + + if( found) { + if(!s2p((char*)(o)+R->sz, R->type, found)) + return 0; + } + } + return o; +} +obj *obj_loadini(void *o, const char *ini) { // @testme + return obj_mergeini(obj_zero(o), ini); +} + +char *obj_savejson(const void *o) { + char *j = 0; + const char *T = obj_type(o); + for each_member(T,R) { + const char *sav = p2s(R->type,(char*)(o)+R->sz); + if(!sav) return FREE(j), NULL; + char is_string = !strcmp(R->type,"char*") || !strcmp(R->type,"string"); + strcatf(&j," %s: %s%s%s,\n", R->name,is_string?"\"":"",sav,is_string?"\"":"" ); + } + char *out = va("%s: { // v100\n%s}\n", T,j); + FREE(j); +#if is(debug) + json5 root = { 0 }; + char *error = json5_parse(&root, va("%s", out), 0); + assert( !error ); + json5_free(&root); #endif + return out; +} +obj *obj_mergejson(void *o, const char *json) { + // @fixme: va() call below could be optimized out since we could figure it out if json was internally provided (via va or strdup), or user-provided + json5 root = { 0 }; + char *error = json5_parse(&root, va("%s", json), 0); // @todo: parse version comment + if( !error && root.type == JSON5_OBJECT && root.count == 1 ) { + json5 *n = &root.nodes[0]; + char *T = n->name; + for each_member(T,R) { + for( int i = 0; i < n->count; ++i ) { + if( !strcmp(R->name, n->nodes[i].name) ) { + void *p = (char*)o + R->sz; + /**/ if( n->nodes[i].type == JSON5_UNDEFINED ) {} + else if( n->nodes[i].type == JSON5_NULL ) { + *(uintptr_t*)(p) = (uintptr_t)0; + } + else if( n->nodes[i].type == JSON5_BOOL ) { + *(bool*)p = n->nodes[i].boolean; + } + else if( n->nodes[i].type == JSON5_INTEGER ) { + if( strstr(R->type, "64" ) ) + *(int64_t*)p = n->nodes[i].integer; + else + *(int*)p = n->nodes[i].integer; + } + else if( n->nodes[i].type == JSON5_STRING ) { + char *s = 0; + strcatf(&s, "%s", n->nodes[i].string); + *(uintptr_t*)(p) = (uintptr_t)s; + } + else if( n->nodes[i].type == JSON5_REAL ) { + if( R->type[0] == 'f' ) + *(float*)(p) = n->nodes[i].real; + else + *(double*)(p) = n->nodes[i].real; + } + else if( n->nodes[i].type == JSON5_OBJECT ) {} + else if( n->nodes[i].type == JSON5_ARRAY ) {} + break; + } + } + } + } + json5_free(&root); + return error ? 0 : o; +} +obj *obj_loadjson(void *o, const char *json) { // @testme + return obj_mergejson(obj_zero(o), json); +} + +char *obj_savebin(const void *o) { // PACKMSG("ss", "entity_v1", quark(self->objnameid)); // = PACKMSG("p", obj_data(&b), (uint64_t)obj_size(&b)); + int len = cobs_bounds(obj_size(o)); + char *sav = va("%*.s", len, ""); + len = cobs_encode(obj_datac(o), obj_size(o), sav, len); + sav[len] = '\0'; + return sav; +} +obj *obj_mergebin(void *o, const char *sav) { // UNPACKMSG(sav, "p", obj_data(c), (uint64_t)obj_size(c)); + int outlen = cobs_decode(sav, strlen(sav), obj_data(o), obj_size(o)); + return outlen != obj_size(o) ? NULL : o; +} +obj *obj_loadbin(void *o, const char *sav) { + return obj_mergebin(obj_zero(o), sav); +} + +char *obj_savempack(const void *o) { // @todo + return ""; +} +obj *obj_mergempack(void *o, const char *sav) { // @todo + return 0; +} +obj *obj_loadmpack(void *o, const char *sav) { // @todo + return obj_mergempack(obj_zero(o), sav); +} + +static __thread array(char*) obj_stack; +int obj_push(const void *o) { + char *bin = STRDUP(obj_savebin(o)); + array_push(obj_stack, bin); + return 1; +} +int obj_pop(void *o) { + char *bin = *array_pop(obj_stack); + int rc = !!obj_loadbin(o, bin); + return FREE(bin), rc; +} + +static +void test_obj_serialization(void *o1, void *o2) { + obj* b = (obj*)o1; + obj* c = (obj*)o2; + + char *json = obj_savejson(b); // puts(json); + test( json[0] ); + char *ini = obj_saveini(b); // puts(ini); + test( ini[0] ); + char *bin = obj_savebin(b); // puts(bin); + test( bin[0] ); + + obj_push(c); + + test( obj_copy(c,b) ); + test( obj_comp(b,c) == 0 ) || obj_hexdump(b) & obj_hexdump(c); + + test( obj_zero(c) ); + test( obj_comp(c,b) != 0 ) || obj_hexdump(c); + test( obj_loadbin(c, bin) ); + test( obj_comp(c,b) == 0 ) || obj_hexdump(c) & obj_hexdump(b); + + test( obj_zero(c) ); + test( obj_comp(c,b) != 0 ) || obj_hexdump(c); + test( obj_loadini(c, ini) ); + test( obj_comp(c,b) == 0 ) || obj_hexdump(c) & obj_hexdump(b); + + test( obj_zero(c) ); + test( obj_comp(c,b) != 0 ) || obj_hexdump(c); + test( obj_loadjson(c, json) ); + test( obj_comp(c,b) == 0 ) || obj_hexdump(c) & obj_hexdump(b); + + obj_pop(c); + obj_hexdump(c); +} + +// ---------------------------------------------------------------------------- +// components + +bool obj_addcomponent(void *object, unsigned c, void *ptr) { + entity *e = (entity*)object; + e->cflags |= (3ULL << c); + e->c[c & (OBJCOMPONENTS_MAX-1)] = ptr; + return 1; +} +bool obj_hascomponent(void *object, unsigned c) { + entity *e = (entity*)object; + return !!(e->cflags & (3ULL << c)); +} +void* obj_getcomponent(void *object, unsigned c) { + entity *e = (entity*)object; + return e->c[c & (OBJCOMPONENTS_MAX-1)]; +} +bool obj_delcomponent(void *object, unsigned c) { + entity *e = (entity*)object; + e->cflags &= ~(3ULL << c); + e->c[c & (OBJCOMPONENTS_MAX-1)] = NULL; + return 1; +} +bool obj_usecomponent(void *object, unsigned c) { + entity *e = (entity*)object; + e->cflags |= (1ULL << c); + return 1; +} +bool obj_offcomponent(void *object, unsigned c) { + entity *e = (entity*)object; + e->cflags &= ~(1ULL << c); + return 0; +} + +char *entity_save(entity *self) { + char *sav = obj_saveini(self); + return sav; +} + +AUTORUN { + STRUCT(entity, uintptr_t, cflags); + +// struct { OBJHEADER union { struct { uintptr_t objenabled : 32, objflagged : 32; }; uintptr_t cflags; }; void* c[32]; }; + + obj_extend(entity, save); +} + +static +void test_obj_ecs() { + entity *e = obj_new(entity); + puts(obj_save(e)); + + for( int i = 0; i < 32; ++i) test(0 == obj_hascomponent(e, i)); + for( int i = 0; i < 32; ++i) test(1 == obj_addcomponent(e, i, NULL)); + for( int i = 0; i < 32; ++i) test(1 == obj_hascomponent(e, i)); + for( int i = 0; i < 32; ++i) test(1 == obj_delcomponent(e, i)); + for( int i = 0; i < 32; ++i) test(0 == obj_hascomponent(e, i)); +} + +// ---------------------------------------------------------------------------- +// reflection + +void *obj_clone(const void *src) { + obj *ptr = obj_malloc( sizeof(obj) + obj_size(src) + sizeof(array(obj*)) ); + ptr->objheader = ((const obj *)src)->objheader; + obj_loadini(ptr, obj_saveini(src)); + return ptr; +} + +void* obj_merge(void *dst, const void *src) { // @testme + char *bin = obj_savebin(src); + return obj_mergebin(dst, bin); +} + +void* obj_mutate(void **dst, const void *src) { +#if 0 + // mutate a class. ie, convert a given object class into a different one, + // while preserving the original metas, components and references as much as possible. + // @todo iterate per field + + if(!*dst_) return *dst_ = obj_clone(src); + + void *dst = *dst_; + dtor(dst); + + unsigned src_sz = obj_sizeof(src); + unsigned src_id = obj_id(src); + + void *dst_ptr = *((void**)dst - 1); + unsigned payload = (OBJPAYLOAD16(dst_ptr) & 255) | src_id << 8; + FREE( OBJUNBOX(dst_ptr) ); + *((void**)dst - 1) = OBJBOX( STRDUP( OBJUNBOX(*((void**)src - 1)) ), payload); + + void *base = (void*)((void**)dst - 1); + base = REALLOC(base, src_sz + sizeof(void*)); + *dst_ = (char*)base + sizeof(void*); + dst = (char*)base + sizeof(void*); + memcpy(dst, src, src_sz); + + ctor(dst); +#endif + return dst; +} + +void *obj_make(const char *str) { + const char *T; + const char *I = strchr(str, '['); // is_ini + const char *J = strchr(str, '{'); // is_json + if( !I && !J ) return 0; + else if( I && !J ) T = I; + else if( !I && J ) T = J; + else T = I < J ? I : J; + + char name[64] = {0}; + if( sscanf(T+1, T == I ? "%64[^]]" : "%64[^:=]", &name) != 1 ) return 0; + + int has_components = 0; // @todo: support entities too + + unsigned Tid = intern(name); + reflect_init(); + reflect_t *found = map_find(reflects, Tid); + if(!found) return 0; + + obj *ptr = CALLOC(1, found->sz + has_components * sizeof(array(obj*))); + void *ret = (T == I ? obj_mergeini : obj_mergejson)(ptr, str); + OBJTYPES[ found->objtype ] = found->name; + OBJ_CTOR_PTR(ptr,1,found->id,found->sz,found->objtype); + + return ptr; // returns partial construction as well. @todo: just return `ret` for a more strict built/failed policy +} diff --git a/engine/split/v4k_obj.h b/engine/split/v4k_obj.h index 512848c..1e74673 100644 --- a/engine/split/v4k_obj.h +++ b/engine/split/v4k_obj.h @@ -1,57 +1,306 @@ -// ----------------------------------------------------------------------------- -// semantic versioning in a single byte (octal) +// C objects framework // - rlyeh, public domain. // -// - single octal byte that represents semantic versioning (major.minor.patch). -// - allowed range [0000..0377] ( <-> [0..255] decimal ) -// - comparison checks only major.minor tuple as per convention. +// ## object limitations +// - 8-byte overhead per object +// - XX-byte overhead per object-entity +// - 32 components max per object-entity +// - 256 classes max per game +// - 256 references max per object +// - 1024K bytes max per object +// - 8 generations + 64K IDs per running instance (19-bit IDs) +// - support for pragma pack(1) structs not enabled by default. -API int semver( int major, int minor, int patch ); -API int semvercmp( int v1, int v2 ); +/* /!\ if you plan to use pragma pack(1) on any struct, you need #define OBJ_MIN_PRAGMAPACK_BITS 0 at the expense of max class size /!\ */ +#ifndef OBJ_MIN_PRAGMAPACK_BITS +//#define OBJ_MIN_PRAGMAPACK_BITS 3 // allows pragma packs >= 8. objsizew becomes 7<<3, so 1024 bytes max per class (default) +#define OBJ_MIN_PRAGMAPACK_BITS 1 // allows pragma packs >= 2. objsizew becomes 7<<1, so 256 bytes max per class +//#define OBJ_MIN_PRAGMAPACK_BITS 0 // allows pragma packs >= 1. objsizew becomes 7<<0, so 128 bytes max per class +#endif -#define SEMVER(major,minor,patch) (0100 * (major) + 010 * (minor) + (patch)) -#define SEMVERCMP(v1,v2) (((v1) & 0110) - ((v2) & 0110)) -#define SEMVERFMT "%03o" +#define OBJHEADER \ + union { \ + uintptr_t objheader; \ + struct { \ + uintptr_t objtype:8; \ + uintptr_t objheap:1; \ + uintptr_t objsizew:7; \ + uintptr_t objrefs:8; \ + uintptr_t objcomps:1; /* << can be removed? check payload ptr instead? */ \ + uintptr_t objnameid:16; \ + uintptr_t objid:ID_INDEX_BITS+ID_COUNT_BITS; /*16+3*/ \ + uintptr_t objunused:64-8-7-1-1-8-16-ID_INDEX_BITS-ID_COUNT_BITS; /*4*/ \ + }; \ + }; -// ----------------------------------------------------------------------------- -// storage types. refer to vec2i/3i, vec2/3/4 if you plan to do math operations +#define OBJ \ + struct { OBJHEADER }; -typedef struct byte2 { uint8_t x,y; } byte2; -typedef struct byte3 { uint8_t x,y,z; } byte3; -typedef struct byte4 { uint8_t x,y,z,w; } byte4; +// ---------------------------------------------------------------------------- +// syntax sugars -typedef struct int2 { int x,y; } int2; -typedef struct int3 { int x,y,z; } int3; -typedef struct int4 { int x,y,z,w; } int4; +#ifdef OBJTYPE +#undef OBJTYPE +#endif -typedef struct uint2 { unsigned int x,y; } uint2; -typedef struct uint3 { unsigned int x,y,z; } uint3; -typedef struct uint4 { unsigned int x,y,z,w; } uint4; +#define OBJTYPE(T) \ + OBJTYPE_##T -typedef struct float2 { float x,y; } float2; -typedef struct float3 { float x,y,z; } float3; -typedef struct float4 { float x,y,z,w; } float4; +#define OBJTYPEDEF(NAME,N) \ + enum { OBJTYPE(NAME) = N }; \ + STATIC_ASSERT( N <= 255 ); \ + STATIC_ASSERT( (sizeof(NAME) & ((1<objheader = HEAP ? id_make(PTR) : 0, /*should assign to .objid instead. however, id_make() returns shifted bits already*/ \ + (PTR)->objnameid = (OBJ_NAMEID), \ + (PTR)->objtype = (OBJ_TYPE), \ + (PTR)->objheap = (HEAP), \ + (PTR)->objsizew = (SIZEOF_OBJ>>OBJ_MIN_PRAGMAPACK_BITS)) +#define OBJ_CTOR_PTR(PTR,HEAP,OBJ_NAMEID,SIZEOF_OBJ,OBJ_TYPE) ( \ + OBJ_CTOR_HDR(PTR,HEAP,OBJ_NAMEID,SIZEOF_OBJ,OBJ_TYPE), \ + obj_ctor(PTR)) +#define OBJ_CTOR(TYPE, NAME, HEAP, PAYLOAD_SIZE, ...) (TYPE*)( \ + objtmp = (HEAP ? MALLOC(sizeof(TYPE)+(PAYLOAD_SIZE)) : ALLOCA(sizeof(TYPE)+(PAYLOAD_SIZE))), \ + *(TYPE*)objtmp = ((TYPE){ {0}, __VA_ARGS__}), \ + ((PAYLOAD_SIZE) ? memset((char*)objtmp + sizeof(TYPE), 0, (PAYLOAD_SIZE)) : objtmp), \ + ( OBJTYPES[ OBJTYPE(TYPE) ] = #TYPE ), \ + OBJ_CTOR_PTR(objtmp, HEAP,intern(NAME),sizeof(TYPE),OBJTYPE(TYPE)), \ + ifdef(debug, (obj_printf)(objtmp, va("%s", callstack(+16))), 0), \ + objtmp) + +#define obj(TYPE, ...) obj_ext(TYPE, #TYPE, __VA_ARGS__) +#define obj_ext(TYPE, NAME, ...) *OBJ_CTOR(TYPE, NAME, 0, sizeof(array(obj*)), __VA_ARGS__) + +#define obj_new(TYPE, ...) obj_new_ext(TYPE, #TYPE, __VA_ARGS__) +#define obj_new_ext(TYPE, NAME, ...) OBJ_CTOR(TYPE, NAME, 1, sizeof(array(obj*)), __VA_ARGS__) + +void* obj_malloc(unsigned sz); +void* obj_free(void *o); + +// ---------------------------------------------------------------------------- +// obj generics. can be extended. + +#define obj_ctor(o,...) obj_method(ctor, o, ##__VA_ARGS__) +#define obj_dtor(o,...) obj_method(dtor, o, ##__VA_ARGS__) + +#define obj_save(o,...) obj_method(save, o, ##__VA_ARGS__) +#define obj_load(o,...) obj_method(load, o, ##__VA_ARGS__) + +#define obj_test(o,...) obj_method(test, o, ##__VA_ARGS__) + +#define obj_init(o,...) obj_method(init, o, ##__VA_ARGS__) +#define obj_quit(o,...) obj_method(quit, o, ##__VA_ARGS__) +#define obj_tick(o,...) obj_method(tick, o, ##__VA_ARGS__) +#define obj_draw(o,...) obj_method(draw, o, ##__VA_ARGS__) + +#define obj_lerp(o,...) obj_method(lerp, o, ##__VA_ARGS__) +#define obj_edit(o,...) obj_method(edit, o, ##__VA_ARGS__) + +// --- syntax sugars + +#define obj_extend(T,func) (obj_##func[OBJTYPE(T)] = (void*)T##_##func) +#define obj_method(method,o,...) (obj_##method[((obj*)(o))->objtype](o,##__VA_ARGS__)) // (obj_##method[((obj*)(o))->objtype]((o), ##__VA_ARGS__)) + +#define obj_vtable(func,RC,...) RC macro(obj_##func)(){ __VA_ARGS__ }; RC (*obj_##func[256])() = { REPEAT256(macro(obj_##func)) }; +#define obj_vtable_null(func,RC) RC (*obj_##func[256])() = { 0 }; // null virtual table. will crash unless obj_extend'ed + +#define REPEAT16(f) f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f +#define REPEAT64(f) REPEAT16(f),REPEAT16(f),REPEAT16(f),REPEAT16(f) +#define REPEAT256(f) REPEAT64(f),REPEAT64(f),REPEAT64(f),REPEAT64(f) + +// --- declare vtables + +API extern void (*obj_ctor[256])(); ///- +API extern void (*obj_dtor[256])(); ///- + +API extern char* (*obj_save[256])(); ///- +API extern bool (*obj_load[256])(); ///- +API extern int (*obj_test[256])(); ///- + +API extern int (*obj_init[256])(); ///- +API extern int (*obj_quit[256])(); ///- +API extern int (*obj_tick[256])(); ///- +API extern int (*obj_draw[256])(); ///- + +API extern int (*obj_lerp[256])(); ///- +API extern int (*obj_edit[256])(); ///- + +// ---------------------------------------------------------------------------- +// core + +API uintptr_t obj_header(const void *o); + +API uintptr_t obj_id(const void *o); +API const char* obj_name(const void *o); + +API unsigned obj_typeid(const void *o); +API const char* obj_type(const void *o); + +API int obj_sizeof(const void *o); +API int obj_size(const void *o); // size of all members together in struct. may include padding bytes. + +API char* obj_data(void *o); // pointer to the first member in struct +API const char* obj_datac(const void *o); // const pointer to the first struct member + +API void* obj_payload(const void *o); // pointer right after last member in struct +API void* obj_zero(void *o); // reset all object members + +// ---------------------------------------------------------------------------- +// refcounting + +API void* obj_ref(void *oo); +API void* obj_unref(void *oo); + +// ---------------------------------------------------------------------------- +// scene tree + +// non-recursive +#define each_objchild(p,t,o) \ + (array(obj*)* children = obj_children(p); children; children = 0) \ + for(int _i = 1, _end = array_count(*children); _i < _end; ++_i) \ + for(t *o = (t *)((*children)[_i]); o && 0[*children]; o = 0) + +API obj* obj_detach(void *c); +API obj* obj_attach(void *o, void *c); + +API obj* obj_root(const void *o); +API obj* obj_parent(const void *o); +API array(obj*)*obj_children(const void *o); +API array(obj*)*obj_siblings(const void *o); + +API int obj_dumptree(const void *o); + +// ---------------------------------------------------------------------------- +// metadata + +API const char* obj_metaset(const void *o, const char *key, const char *value); +API const char* obj_metaget(const void *o, const char *key); + +// ---------------------------------------------------------------------------- +// stl + +API void* obj_swap(void *dst, void *src); +API void* obj_copy_fast(void *dst, const void *src); +API void* obj_copy(void *dst, const void *src); + +API int obj_comp_fast(const void *a, const void *b); +API int obj_comp(const void *a, const void *b); +API int obj_lesser(const void *a, const void *b); +API int obj_greater(const void *a, const void *b); +API int obj_equal(const void *a, const void *b); + +API uint64_t obj_hash(const void *o); + +// ---------------------------------------------------------------------------- +// debug + +API bool obj_hexdump(const void *oo); +API int obj_print(const void *o); + +API int obj_printf(const void *o, const char *text); +API int obj_console(const void *o); // obj_output() ? + +#define obj_printf(o, ...) obj_printf(o, va(__VA_ARGS__)) + +// ---------------------------------------------------------------------------- +// serialization + +API char* obj_saveini(const void *o); +API obj* obj_mergeini(void *o, const char *ini); +API obj* obj_loadini(void *o, const char *ini); + +API char* obj_savejson(const void *o); +API obj* obj_mergejson(void *o, const char *json); +API obj* obj_loadjson(void *o, const char *json); + +API char* obj_savebin(const void *o); +API obj* obj_mergebin(void *o, const char *sav); +API obj* obj_loadbin(void *o, const char *sav); + +API char* obj_savempack(const void *o); // @todo +API obj* obj_mergempack(void *o, const char *sav); // @todo +API obj* obj_loadmpack(void *o, const char *sav); // @todo + +API int obj_push(const void *o); +API int obj_pop(void *o); + +// ---------------------------------------------------------------------------- +// components + +API bool obj_addcomponent(void *object, unsigned c, void *ptr); +API bool obj_hascomponent(void *object, unsigned c); +API void* obj_getcomponent(void *object, unsigned c); +API bool obj_delcomponent(void *object, unsigned c); +API bool obj_usecomponent(void *object, unsigned c); +API bool obj_offcomponent(void *object, unsigned c); + +API char* entity_save(entity *self); + +// ---------------------------------------------------------------------------- +// reflection + +#define each_objmember(oo,TYPE,NAME,PTR) \ + (array(reflect_t) *found_ = members_find(quark(((obj*)oo)->objnameid)); found_; found_ = 0) \ + for(int it_ = 0, end_ = array_count(*found_); it_ != end_; ++it_ ) \ + for(reflect_t *R = &(*found_)[it_]; R; R = 0 ) \ + for(const char *NAME = R->name, *TYPE = R->type; NAME || TYPE; ) \ + for(void *PTR = ((char*)oo) + R->sz ; NAME || TYPE ; NAME = TYPE = 0 ) + +API void* obj_clone(const void *src); +API void* obj_merge(void *dst, const void *src); // @testme +API void* obj_mutate(void **dst, const void *src); +API void* obj_make(const char *str); + +// built-ins + +typedef enum OBJTYPE_BUILTINS { + OBJTYPE_obj = 0, + OBJTYPE_entity = 1, + OBJTYPE_vec2 = 2, + OBJTYPE_vec3 = 3, + OBJTYPE_vec4 = 4, + OBJTYPE_quat = 5, + OBJTYPE_mat33 = 6, + OBJTYPE_mat34 = 7, + OBJTYPE_mat44 = 8, + OBJTYPE_vec2i = 9, + OBJTYPE_vec3i = 10, +} OBJTYPE_BUILTINS; -#define double2(x,y) M_CAST(double2, (double)(x), (double)(y) ) -#define double3(x,y,z) M_CAST(double3, (double)(x), (double)(y), (double)(z) ) -#define double4(x,y,z,w) M_CAST(double4, (double)(x), (double)(y), (double)(z), (double)(w) ) diff --git a/engine/split/v4k_pack.c b/engine/split/v4k_pack.c index 6bbb353..7637b99 100644 --- a/engine/split/v4k_pack.c +++ b/engine/split/v4k_pack.c @@ -1,3 +1,48 @@ +// ----------------------------------------------------------------------------- +// semantic versioning in a single byte (octal) +// - rlyeh, public domain. +// +// - single octal byte that represents semantic versioning (major.minor.patch). +// - allowed range [0000..0377] ( <-> [0..255] decimal ) +// - comparison checks only major.minor tuple as per convention. + +int semver( int major, int minor, int patch ) { + return SEMVER(major, minor, patch); +} +int semvercmp( int v1, int v2 ) { + return SEMVERCMP(v1, v2); +} + +#if 0 +AUTORUN { + for( int i= 0; i <= 255; ++i) printf(SEMVERFMT ",", i); + puts(""); + + printf(SEMVERFMT "\n", semver(3,7,7)); + printf(SEMVERFMT "\n", semver(2,7,7)); + printf(SEMVERFMT "\n", semver(1,7,7)); + printf(SEMVERFMT "\n", semver(0,7,7)); + + printf(SEMVERFMT "\n", semver(3,7,1)); + printf(SEMVERFMT "\n", semver(2,5,3)); + printf(SEMVERFMT "\n", semver(1,3,5)); + printf(SEMVERFMT "\n", semver(0,1,7)); + + assert( semvercmp( 0357, 0300 ) > 0 ); + assert( semvercmp( 0277, 0300 ) < 0 ); + assert( semvercmp( 0277, 0200 ) > 0 ); + assert( semvercmp( 0277, 0100 ) < 0 ); + assert( semvercmp( 0076, 0070 ) == 0 ); + assert( semvercmp( 0076, 0077 ) == 0 ); + assert( semvercmp( 0176, 0170 ) == 0 ); + assert( semvercmp( 0176, 0177 ) == 0 ); + assert( semvercmp( 0276, 0270 ) == 0 ); + assert( semvercmp( 0276, 0277 ) == 0 ); + assert( semvercmp( 0376, 0370 ) == 0 ); + assert( semvercmp( 0376, 0377 ) == 0 ); +} +#endif + // ----------------------------------------------------------------------------- // compile-time fourcc, eightcc @@ -25,24 +70,18 @@ char *cc8str(uint64_t x) { // ---------------------------------------------------------------------------- // float conversion (text) -vec2 atof2(const char *s) { - vec2 v = {0}; - sscanf(s, "%f,%f", &v.x, &v.y); - return v; +char* itoa1(int v) { + return va("%d", v); } -vec3 atof3(const char *s) { - vec3 v = {0}; - sscanf(s, "%f,%f,%f", &v.x, &v.y, &v.z); - return v; +char* itoa2(vec2i v) { + return va("%d,%d", v.x,v.y); } -vec4 atof4(const char *s) { - vec4 v = {0}; - sscanf(s, "%f,%f,%f,%f", &v.x, &v.y, &v.z, &v.w); - return v; +char* itoa3(vec3i v) { + return va("%d,%d,%d", v.x,v.y,v.z); } -char* ftoa(float f) { - return va("%f", f); +char* ftoa1(float v) { + return va("%f", v); } char* ftoa2(vec2 v) { return va("%f,%f", v.x, v.y); @@ -54,6 +93,51 @@ char* ftoa4(vec4 v) { return va("%f,%f,%f,%f", v.x, v.y, v.z, v.w); } +float atof1(const char *s) { + char buf[64]; + return sscanf(s, "%64[^]\r\n,}]", buf) == 1 ? (float)eval(buf) : (float)NAN; +} +vec2 atof2(const char *s) { + vec2 v = { 0 }; + char buf1[64],buf2[64]; + int num = sscanf(s, "%64[^]\r\n,}],%64[^]\r\n,}]", buf1, buf2); + if( num > 0 ) v.x = eval(buf1); + if( num > 1 ) v.y = eval(buf2); + return v; +} +vec3 atof3(const char *s) { + vec3 v = {0}; + char buf1[64],buf2[64],buf3[64]; + int num = sscanf(s, "%64[^]\r\n,}],%64[^]\r\n,}],%64[^]\r\n,}]", buf1, buf2, buf3); + if( num > 0 ) v.x = eval(buf1); + if( num > 1 ) v.y = eval(buf2); + if( num > 2 ) v.z = eval(buf3); + return v; +} +vec4 atof4(const char *s) { + vec4 v = {0}; + char buf1[64],buf2[64],buf3[64],buf4[64]; + int num = sscanf(s, "%64[^]\r\n,}],%64[^]\r\n,}],%64[^]\r\n,}],%64[^]\r\n,}]", buf1, buf2, buf3, buf4); + if( num > 0 ) v.x = eval(buf1); + if( num > 1 ) v.y = eval(buf2); + if( num > 2 ) v.z = eval(buf3); + if( num > 3 ) v.w = eval(buf4); + return v; +} + +// @todo: expand this to proper int parsers +int atoi1(const char *s) { + return (int)atof1(s); +} +vec2i atoi2(const char *s) { + vec2 v = atof2(s); + return vec2i( v.x, v.y ); +} +vec3i atoi3(const char *s) { + vec3 v = atof3(s); + return vec3i( v.x, v.y, v.z ); +} + // endianness ----------------------------------------------------------------- // - rlyeh, public domain diff --git a/engine/split/v4k_pack.h b/engine/split/v4k_pack.h index d46da1d..deebb3c 100644 --- a/engine/split/v4k_pack.h +++ b/engine/split/v4k_pack.h @@ -1,3 +1,61 @@ +// ----------------------------------------------------------------------------- +// semantic versioning in a single byte (octal) +// - rlyeh, public domain. +// +// - single octal byte that represents semantic versioning (major.minor.patch). +// - allowed range [0000..0377] ( <-> [0..255] decimal ) +// - comparison checks only major.minor tuple as per convention. + +API int semver( int major, int minor, int patch ); +API int semvercmp( int v1, int v2 ); + +#define SEMVER(major,minor,patch) (0100 * (major) + 010 * (minor) + (patch)) +#define SEMVERCMP(v1,v2) (((v1) & 0110) - ((v2) & 0110)) +#define SEMVERFMT "%03o" + +// ----------------------------------------------------------------------------- +// storage types. refer to vec2i/3i, vec2/3/4 if you plan to do math operations + +typedef struct byte2 { uint8_t x,y; } byte2; +typedef struct byte3 { uint8_t x,y,z; } byte3; +typedef struct byte4 { uint8_t x,y,z,w; } byte4; + +typedef struct int2 { int x,y; } int2; +typedef struct int3 { int x,y,z; } int3; +typedef struct int4 { int x,y,z,w; } int4; + +typedef struct uint2 { unsigned int x,y; } uint2; +typedef struct uint3 { unsigned int x,y,z; } uint3; +typedef struct uint4 { unsigned int x,y,z,w; } uint4; + +typedef struct float2 { float x,y; } float2; +typedef struct float3 { float x,y,z; } float3; +typedef struct float4 { float x,y,z,w; } float4; + +typedef struct double2 { double x,y; } double2; +typedef struct double3 { double x,y,z; } double3; +typedef struct double4 { double x,y,z,w; } double4; + +#define byte2(x,y) M_CAST(byte2, (uint8_t)(x), (uint8_t)(y) ) +#define byte3(x,y,z) M_CAST(byte3, (uint8_t)(x), (uint8_t)(y), (uint8_t)(z) ) +#define byte4(x,y,z,w) M_CAST(byte4, (uint8_t)(x), (uint8_t)(y), (uint8_t)(z), (uint8_t)(w) ) + +#define int2(x,y) M_CAST(int2, (int)(x), (int)(y) ) +#define int3(x,y,z) M_CAST(int3, (int)(x), (int)(y), (int)(z) ) +#define int4(x,y,z,w) M_CAST(int4, (int)(x), (int)(y), (int)(z), (int)(w) ) + +#define uint2(x,y) M_CAST(uint2, (unsigned)(x), (unsigned)(y) ) +#define uint3(x,y,z) M_CAST(uint3, (unsigned)(x), (unsigned)(y), (unsigned)(z) ) +#define uint4(x,y,z,w) M_CAST(uint4, (unsigned)(x), (unsigned)(y), (unsigned)(z), (unsigned)(w) ) + +#define float2(x,y) M_CAST(float2, (float)(x), (float)(y) ) +#define float3(x,y,z) M_CAST(float3, (float)(x), (float)(y), (float)(z) ) +#define float4(x,y,z,w) M_CAST(float4, (float)(x), (float)(y), (float)(z), (float)(w) ) + +#define double2(x,y) M_CAST(double2, (double)(x), (double)(y) ) +#define double3(x,y,z) M_CAST(double3, (double)(x), (double)(y), (double)(z) ) +#define double4(x,y,z,w) M_CAST(double4, (double)(x), (double)(y), (double)(z), (double)(w) ) + // ----------------------------------------------------------------------------- // compile-time fourcc, eightcc @@ -26,16 +84,25 @@ enum { #define cc7(a,b,c,d,e,f,g) cc8(,a,b,c,d,e,f,g) // ---------------------------------------------------------------------------- -// float conversion (text) +// text conversions +API char* ftoa1(float v); +API char* ftoa2(vec2 v); +API char* ftoa3(vec3 v); +API char* ftoa4(vec4 v); + +API float atof1(const char *s); API vec2 atof2(const char *s); API vec3 atof3(const char *s); API vec4 atof4(const char *s); -API char* ftoa(float f); -API char* ftoa2(vec2 v); -API char* ftoa3(vec3 v); -API char* ftoa4(vec4 v); +API char* itoa1(int v); +API char* itoa2(vec2i v); +API char* itoa3(vec3i v); + +API int atoi1(const char *s); +API vec2i atoi2(const char *s); +API vec3i atoi3(const char *s); // ---------------------------------------------------------------------------- // endianness diff --git a/engine/split/v4k_reflect.c b/engine/split/v4k_reflect.c index 5d698ff..9293c6c 100644 --- a/engine/split/v4k_reflect.c +++ b/engine/split/v4k_reflect.c @@ -79,10 +79,10 @@ void *member_findptr(void *obj, const char *T, const char *M) { M = symbol(M); return (char*)obj + member_find(T,M).sz; } -array(reflect_t) members_find(const char *T) { +array(reflect_t)* members_find(const char *T) { reflect_init(); T = symbol(T); - return *map_find(members, intern(T)); + return map_find(members, intern(T)); } diff --git a/engine/split/v4k_reflect.h b/engine/split/v4k_reflect.h index 8add9b8..386b1e3 100644 --- a/engine/split/v4k_reflect.h +++ b/engine/split/v4k_reflect.h @@ -43,14 +43,14 @@ API void * function_find(const char *F); API reflect_t member_find(const char *T, const char *M); /// find specific member API void * member_findptr(void *obj, const char *T, const char *M); // @deprecate -API array(reflect_t) members_find(const char *T); +API array(reflect_t)* members_find(const char *T); // iterate members in a struct #define each_member(T,R) \ - (array(reflect_t)*found_ = map_find(members, intern(T)); found_; found_ = 0) \ + (array(reflect_t) *found_ = members_find(T); found_; found_ = 0) \ for(int it_ = 0, end_ = array_count(*found_); it_ != end_; ++it_ ) \ - for(reflect_t *R = (*found_)+it_; R; R = 0 ) + for(reflect_t *R = &(*found_)[it_]; R; R = 0 ) // private api, still exposed diff --git a/engine/split/v4k_render.c b/engine/split/v4k_render.c index 2549f0a..26f5fad 100644 --- a/engine/split/v4k_render.c +++ b/engine/split/v4k_render.c @@ -533,13 +533,13 @@ void shader_colormap(const char *name, colormap_t c ) { // colors unsigned rgba( uint8_t r, uint8_t g, uint8_t b, uint8_t a ) { - return (unsigned)r << 24 | g << 16 | b << 8 | a; + return (unsigned)a << 24 | b << 16 | g << 8 | r; } unsigned bgra( uint8_t b, uint8_t g, uint8_t r, uint8_t a ) { return rgba(r,g,b,a); } -float alpha( unsigned rgba ) { - return ( rgba >> 24 ) / 255.f; +unsigned alpha( unsigned rgba ) { + return rgba >> 24; } unsigned rgbaf(float r, float g, float b, float a) { diff --git a/engine/split/v4k_render.h b/engine/split/v4k_render.h index 1760c9a..f392d9b 100644 --- a/engine/split/v4k_render.h +++ b/engine/split/v4k_render.h @@ -15,7 +15,7 @@ API unsigned rgba( uint8_t r, uint8_t g, uint8_t b, uint8_t a ); API unsigned bgra( uint8_t b, uint8_t g, uint8_t r, uint8_t a ); API unsigned rgbaf( float r, float g, float b, float a ); API unsigned bgraf( float b, float g, float r, float a ); -API float alpha( unsigned rgba ); +API unsigned alpha( unsigned rgba ); #define RGBX(rgb,x) ( ((rgb)&0xFFFFFF) | (((unsigned)(x))<<24) ) #define RGB3(r,g,b) ( (255<<24) | ((r)<<16) | ((g)<<8) | (b) ) diff --git a/engine/split/v4k_string.h b/engine/split/v4k_string.h index eb7c3e8..7ef04a9 100644 --- a/engine/split/v4k_string.h +++ b/engine/split/v4k_string.h @@ -79,13 +79,13 @@ API array(uint32_t) string32( const char *utf8 ); /// convert from utf8 to utf32 // ## string interning (quarks) // - rlyeh, public domain. -unsigned intern( const char *string ); -const char *quark( unsigned key ); +API unsigned intern( const char *string ); +API const char *quark( unsigned key ); typedef struct quarks_db { array(char) blob; array(vec2i) entries; } quarks_db; -unsigned quark_intern( quarks_db*, const char *string ); -const char *quark_string( quarks_db*, unsigned key ); +API unsigned quark_intern( quarks_db*, const char *string ); +API const char *quark_string( quarks_db*, unsigned key ); diff --git a/engine/split/v4k_system.c b/engine/split/v4k_system.c index c4f2723..e049394 100644 --- a/engine/split/v4k_system.c +++ b/engine/split/v4k_system.c @@ -279,7 +279,7 @@ void trap_on_abort(int signal) { exit(-1); } void trap_on_debug(int signal) { - breakpoint("Error: unexpected signal"); + alert("Error: unexpected signal"), breakpoint(); fprintf(stderr, "Error: unexpected signal %s (%d)\n%s\n", trap_name(signal), signal, callstack(16)); exit(-1); } @@ -549,7 +549,7 @@ void tty_color(unsigned color) { } #endif if( color ) { - // if( color == RED ) breakpoint("break on RED"); // debug + // if( color == RED ) alert("break on error message (RED)"), breakpoint(); // debug unsigned r = (color >> 16) & 255; unsigned g = (color >> 8) & 255; unsigned b = (color >> 0) & 255; @@ -668,6 +668,9 @@ static void debugbreak(void) { // break if debugger present #endif void alert(const char *message) { // @todo: move to app_, besides die() + window_visible(false); + message = message[0] == '!' ? (const char*)va("%s\n%s", message+1, callstack(+48)) : message; + #if is(win32) MessageBoxA(0, message, 0,0); #elif is(ems) @@ -678,18 +681,12 @@ void alert(const char *message) { // @todo: move to app_, besides die() #elif is(osx) system(va("osascript -e 'display alert \"Alert\" message \"%s\"'", message)); #endif -} -void breakpoint(const char *reason) { - window_visible(false); - if( reason ) { - const char *fulltext = reason[0] == '!' ? va("%s\n%s", reason+1, callstack(+48)) : reason; - PRINTF("%s", fulltext); - - (alert)(fulltext); - } - debugbreak(); window_visible(true); + } + +void breakpoint() { + debugbreak(); } bool has_debugger() { @@ -752,7 +749,9 @@ int (PANIC)(const char *error, const char *file, int line) { tty_color(0); - breakpoint(error); + alert(error); + breakpoint(); + exit(-line); return 1; } @@ -904,10 +903,12 @@ const char* app_savefile() { // ---------------------------------------------------------------------------- // tests -static __thread int test_oks, test_errs, test_once; -static void test_exit(void) { fprintf(stderr, "%d/%d tests passed\n", test_oks, test_oks+test_errs); } +static __thread int test_oks, test_errors, test_once; +static void test_exit(void) { fprintf(stderr, "%d/%d tests passed\n", test_oks, test_oks+test_errors); } int (test)(const char *file, int line, const char *expr, bool result) { + static int breakon = -1; if(breakon<0) breakon = optioni("--test-break", 0); + if( breakon == (test_oks+test_errors+1) ) alert("user requested to break on this test"), breakpoint(); test_once = test_once || !(atexit)(test_exit); - test_oks += result, test_errs += !result; + test_oks += result, test_errors += !result; return (result || (tty_color(RED), fprintf(stderr, "(Test `%s` failed %s:%d)\n", expr, file, line), tty_color(0), 0) ); } diff --git a/engine/split/v4k_system.h b/engine/split/v4k_system.h index c012595..021ee92 100644 --- a/engine/split/v4k_system.h +++ b/engine/split/v4k_system.h @@ -49,7 +49,7 @@ API void die(const char *message); API void alert(const char *message); API void hexdump( const void *ptr, unsigned len ); API void hexdumpf( FILE *fp, const void *ptr, unsigned len, int width ); -API void breakpoint(const char *optional_reason); +API void breakpoint(); API bool has_debugger(); API void trap_install(void); diff --git a/engine/split/v4k_time.h b/engine/split/v4k_time.h index 1b2a345..5eaacc1 100644 --- a/engine/split/v4k_time.h +++ b/engine/split/v4k_time.h @@ -24,7 +24,7 @@ API void timer_destroy(unsigned timer_handle); // // also similar to a mongo object id, 12 bytes as follows: // - 4-byte timestamp (ss). epoch: Tuesday, 12 September 2023 6:06:56 -// - 2-byte (machine or app hash) +// - 2-byte (machine, hash or app id) // - 2-byte (thread-id) // - 4-byte (rand counter, that gets increased at every id creation) diff --git a/engine/split/v4k_ui.c b/engine/split/v4k_ui.c index 2180ca4..d2dae75 100644 --- a/engine/split/v4k_ui.c +++ b/engine/split/v4k_ui.c @@ -5,7 +5,6 @@ #define UI_FONT_ENUM(carlito,b612) b612 // carlito -#define UI_FONT_ICONS "MaterialIconsSharp-Regular.otf" // "MaterialIconsOutlined-Regular.otf" "MaterialIcons-Regular.ttf" // #define UI_FONT_REGULAR UI_FONT_ENUM("Carlito", "B612") "-Regular.ttf" #define UI_FONT_HEADING UI_FONT_ENUM("Carlito", "B612") "-BoldItalic.ttf" #define UI_FONT_TERMINAL UI_FONT_ENUM("Inconsolata", "B612Mono") "-Regular.ttf" @@ -76,12 +75,16 @@ static void nk_config_custom_fonts() { } // ...with icons embedded on it. - - for( char *data = vfs_load(UI_FONT_ICONS, &datalen); data; data = 0 ) { - static const nk_rune icon_range[] = {UI_ICON_MIN, UI_ICON_MED /*MAX*/, 0}; - + static struct icon_font { + const char *file; nk_rune range[3]; + } icons[] = { + {"MaterialIconsSharp-Regular.otf", {UI_ICON_MIN, UI_ICON_MED /*MAX*/, 0}}, // "MaterialIconsOutlined-Regular.otf" "MaterialIcons-Regular.ttf" + {"materialdesignicons-webfont.ttf", {0xF68C /*ICON_MIN_MDI*/, 0xF1C80/*ICON_MAX_MDI*/, 0}}, + }; + for( int f = 0; f < countof(icons); ++f ) + for( char *data = vfs_load(icons[f].file, &datalen); data; data = 0 ) { struct nk_font_config cfg = nk_font_config(UI_ICON_FONTSIZE); - cfg.range = icon_range; // nk_font_default_glyph_ranges(); + cfg.range = icons[f].range; // nk_font_default_glyph_ranges(); cfg.merge_mode = 1; cfg.spacing.x += UI_ICON_SPACING_X; diff --git a/engine/split/v4k_window.c b/engine/split/v4k_window.c index 70dc0c0..8fb248c 100644 --- a/engine/split/v4k_window.c +++ b/engine/split/v4k_window.c @@ -271,7 +271,8 @@ void glNewFrame() { } bool window_create_from_handle(void *handle, float scale, unsigned flags) { - ifdef(debug, if( flag("--tests") ) exit(0)); + // abort run if any test suite failed in unit-test mode + ifdef(debug, if( flag("--test-only") ) exit( test_errors ? -test_errors : 0 )); glfw_init(); v4k_init(); diff --git a/engine/v4k b/engine/v4k index d7ef1f8..ff4aa9d 100644 --- a/engine/v4k +++ b/engine/v4k @@ -199014,7 +199014,7 @@ nk_utf_decode(const char *c, nk_rune *u, int clen) *u = NK_UTF_INVALID; udecoded = nk_utf_decode_byte(c[0], &len); - if (!NK_BETWEEN(len, 1, NK_UTF_SIZE)) + if (!NK_BETWEEN(len, 1, NK_UTF_SIZE+1)) //< @r-lyeh: add +1 for len<=4 return 1; for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { @@ -211149,7 +211149,12 @@ window->is_window_resizing |= layout->flags & NK_WINDOW_SCALE_TOP ? NK_WINDOW_SC } } - ctx->style.cursor_active = ctx->style.cursors[NK_CURSOR_RESIZE_TOP_RIGHT_DOWN_LEFT]; + int icon = //< @r-lyeh + ((layout->flags & NK_WINDOW_SCALE_TOP) && !(layout->flags & NK_WINDOW_SCALE_LEFT)) + || + ((layout->flags & NK_WINDOW_SCALE_LEFT) && !(layout->flags & NK_WINDOW_SCALE_TOP)) + ? NK_CURSOR_RESIZE_TOP_LEFT_DOWN_RIGHT : NK_CURSOR_RESIZE_TOP_RIGHT_DOWN_LEFT; + ctx->style.cursor_active = ctx->style.cursors[icon]; //< @r-lyeh in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.x = scaler.x + scaler.w/2.0f; in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.y = scaler.y + scaler.h/2.0f; } @@ -312982,6 +312987,499 @@ API void ProgressiveMesh(int vert_n, int vert_stride, const float *v, int tri_n, * SOFTWARE. */ #line 0 +#define expr expr2 // 3rd_lua.h +#line 1 "engine/split/3rd_eval.h" +/* A mathematical expression evaluator. + * It uses a recursive descent parser internally. + * Author: Werner Stoop + * This is free and unencumbered software released into the public domain. + * http://unlicense.org/ + */ + +#include +#include +#include /* remember to compile with -lm */ +#include +#include +#include + +/* Special tokens used by the lexer function lex() + * they've been chosen as non-printable characters + * so that printable characters can be used for other + * purposes + */ +#define TOK_END 0 /* end of text */ +#define TOK_INI 1 /* Initial state */ +#define TOK_ID 2 /* identifier */ +#define TOK_NUM 3 /* number */ + +/* Types of errors */ + // 0 /* "no error" */ +#define ERR_MEMORY 1 /* "out of memory" */ +#define ERR_LEXER 2 /* "unknown token" */ +#define ERR_LONGID 3 /* "identifier too long" */ +#define ERR_VALUE 4 /* "value expected" */ +#define ERR_BRACKET 5 /* "missing ')'" */ +#define ERR_FUNC 6 /* "unknown function" */ +#define ERR_ARGS 7 /* "wrong number of arguments" */ +#define ERR_CONST 8 /* "unknown constant" */ + +/* Other definitions */ +#define MAX_ID_LEN 11 /* Max length of an identifier */ +#define OPERATORS "+-*/%(),^" /* Valid operators */ + +#define EVAL_PI 3.141592654 +#define EVAL_E 2.718281828 +#define EVAL_DEG (EVAL_PI/180) + +/* Internal structure for the parser/evaluator */ +struct eval { + + jmp_buf j; /* For error handling */ + + const char *p; /* Position in the text being parsed */ + + double *st; /* Stack */ + int st_size; /* Stack size */ + int sp; /* Stack pointer */ + + /* The current and next tokens identified by the lexer */ + struct { + int type; /* Type of the token */ + double n_val; /* Numeric value of the previous lexed token */ + char s_val[MAX_ID_LEN]; /* String (identifier) value of the previous lexed token */ + } token[2]; + + int cur_tok; /* Current token, either 0 or 1 (see the comments of lex()) */ +}; + +/* Prototypes */ +static double pop(struct eval *ev); +static void push(struct eval *ev, double d); +static int lex(struct eval *ev); + +/* Prototypes for the recursive descent parser */ +static void expr(struct eval *ev); +static void add_expr(struct eval *ev); +static void mul_expr(struct eval *ev); +static void pow_expr(struct eval *ev); +static void uni_expr(struct eval *ev); +static void bra_expr(struct eval *ev); +static void id_expr(struct eval *ev); +static void num_expr(struct eval *ev); + +/* + * Evaluates a mathemeatical expression + */ +double eval(const char *exp/*, int *ep*/) { +int _ep, *ep = &_ep; + struct eval ev; + double ans = 0.0; + + assert(ep != NULL); + + /* Allocate a stack */ + ev.st_size = 10; + ev.st = CALLOC(ev.st_size, sizeof *ev.st); + if(!ev.st) + { + *ep = ERR_MEMORY; + return NAN; //0.0; + } + ev.sp = 0; + + /* Manage errors */ + *ep = setjmp(ev.j); + if(*ep != 0) + { + FREE(ev.st); + return NAN; //0.0; + } + + /* Initialize the lexer */ + ev.token[0].type = TOK_INI; + ev.token[0].s_val[0] = '\0'; + ev.token[1].type = TOK_INI; + ev.token[1].s_val[0] = '\0'; + ev.cur_tok = 0; + + /* Initialize the parser */ + ev.p = exp; + + /* lex once to initialize the lexer */ + if(lex(&ev) != TOK_END) + { + expr(&ev); + ans = pop(&ev); + } + + FREE(ev.st); + return ans; +} + +/* + * Pushes a value onto the stack, increases the stack size if necessary + */ +static void push(struct eval *ev, double d) { + if(ev->sp == ev->st_size) { + /* Resize the stack by 1.5 */ + double *old = ev->st; + int new_size = ev->st_size + (ev->st_size >> 1); + ev->st = REALLOC(ev->st, new_size); + if(!ev->st) { + ev->st = old; + longjmp(ev->j, ERR_MEMORY); + } + + ev->st_size = new_size; + } + + ev->st[ev->sp++] = d; +} + +// Pops a value from the top of the stack +static double pop(struct eval *ev) { + assert(ev->sp > 0); + return ev->st[--ev->sp]; +} + +// stricmp() is common, but not standard, so I provide my own +static int istrcmp(const char *p, const char *q) { + for(; tolower(p[0]) == tolower(q[0]) && p[0]; p++, q++); + return tolower(p[0]) - tolower(q[0]); +} + +/* + * Lexical analyzer function + * + * In order to implement LL(1), struct eval has an array of two token structures, + * and its cur_tok member is used to point to the _current_ token, while the other + * element contains the _next_ token. This implements a 2 element ring buffer where + * the lexer always writes to the _next_ token so that the recursive descent parser can + * _peek_ at the next token. + */ + +static int lex(struct eval *ev) { + int next_tok; + +start: + /* Cycle the tokens */ + next_tok = ev->cur_tok; + ev->cur_tok = ev->cur_tok?0:1; + + while(isspace(ev->p[0])) ev->p++; + + if(!ev->p[0]) { + /* End of the expression */ + ev->token[next_tok].type = TOK_END; + goto end; + } + else if(isdigit(ev->p[0]) || ev->p[0] == '.') { + /* Number */ + char *endp; + ev->token[next_tok].type = TOK_NUM; + ev->token[next_tok].n_val = strtod(ev->p, &endp); + ev->p = endp; + goto end; + } + else if(isalpha(ev->p[0])) { + /* Identifier */ + int i; + for(i = 0; isalnum(ev->p[0]) && i < MAX_ID_LEN - 1; i++, ev->p++) + ev->token[next_tok].s_val[i] = ev->p[0]; + + if(isalpha(ev->p[0])) longjmp(ev->j, ERR_LONGID); + + ev->token[next_tok].s_val[i] = '\0'; + ev->token[next_tok].type = TOK_ID; + goto end; + } + else if(strchr(OPERATORS, ev->p[0])) { + /* Operator */ + ev->token[next_tok].type = ev->p[0]; + ev->p++; + goto end; + } + else /* Unknown token */ + longjmp(ev->j, ERR_LEXER); + +end: + + /* If this was the first call, cycle the tokens again */ + if(ev->token[ev->cur_tok].type == TOK_INI) + goto start; + + return ev->token[ev->cur_tok].type; +} + +#define EVAL_TYPE(e) (e->token[e->cur_tok].type) +#define EVAL_ERROR(c) longjmp(ev->j, (c)) + +// num_expr ::= NUMBER +static void num_expr(struct eval *ev) { + if(EVAL_TYPE(ev) != TOK_NUM) + EVAL_ERROR(ERR_VALUE); + push(ev, ev->token[ev->cur_tok].n_val); + lex(ev); +} + +// expr ::= add_expr +static void expr(struct eval *ev) { + add_expr(ev); +} + +// add_expr ::= mul_expr [('+'|'-') mul_expr] +static void add_expr(struct eval *ev) { + int t; + mul_expr(ev); + while((t =EVAL_TYPE(ev)) == '+' || t == '-') { + double a,b; + lex(ev); + mul_expr(ev); + b = pop(ev); + a = pop(ev); + + if(t == '+') + push(ev, a + b); + else + push(ev, a - b); + } +} + +// mul_expr ::= pow_expr [('*'|'/'|'%') pow_expr] +static void mul_expr(struct eval *ev) { + int t; + pow_expr(ev); + while((t = EVAL_TYPE(ev)) == '*' || t == '/' || t == '%') { + double a,b; + lex(ev); + pow_expr(ev); + b = pop(ev); + a = pop(ev); + + if(t == '*') + push(ev, a * b); + else if(t == '/') + push(ev, a / b); + else + push(ev, fmod(a, b)); + } +} + +// pow_expr ::= uni_expr ['^' pow_expr] +static void pow_expr(struct eval *ev) { + /* Note that exponentiation is right associative: + 2^3^4 is 2^(3^4), not (2^3)^4 */ + uni_expr(ev); + if(EVAL_TYPE(ev) == '^') { + double a,b; + lex(ev); + pow_expr(ev); + b = pop(ev); + a = pop(ev); + push(ev, pow(a,b)); + } +} + +// uni_expr ::= ['+'|'-'] bra_expr +static void uni_expr(struct eval *ev) { + int t = '+'; + if(EVAL_TYPE(ev) == '-' || EVAL_TYPE(ev) == '+') { + t = EVAL_TYPE(ev); + lex(ev); + } + + bra_expr(ev); + + if(t == '-') { + double a = pop(ev); + push(ev, -a); + } +} + +// bra_expr ::= '(' add_expr ')' | id_expr +static void bra_expr(struct eval *ev) { + if(EVAL_TYPE(ev) == '(') { + lex(ev); + add_expr(ev); + if(EVAL_TYPE(ev) != ')') + EVAL_ERROR(ERR_BRACKET); + lex(ev); + } + else + id_expr(ev); +} + +// id_expr ::= ID '(' add_expr [',' add_expr]* ')' | ID | num_expr +static void id_expr(struct eval *ev) { + int nargs = 0; + char id[MAX_ID_LEN]; + if(EVAL_TYPE(ev) != TOK_ID) { + num_expr(ev); + } else { + strcpy(id, ev->token[ev->cur_tok].s_val); + lex(ev); + if(EVAL_TYPE(ev) != '(') { + /**/ if(!istrcmp(id, "true")) push(ev, 1.0); + else if(!istrcmp(id, "false")) push(ev, 0.0); + else if(!istrcmp(id, "on")) push(ev, 1.0); + else if(!istrcmp(id, "off")) push(ev, 0.0); + // pi - 3.141592654 + else if(!istrcmp(id, "pi")) + push(ev, EVAL_PI); + // e - base of natural logarithms, 2.718281828 + else if(!istrcmp(id, "e")) + push(ev, EVAL_E); + // deg - deg2rad, allows to degree conversion `sin(90*deg) = 1` + else if(!istrcmp(id, "deg")) + push(ev, EVAL_DEG); + else + EVAL_ERROR(ERR_CONST); + } else { + lex(ev); + + while(EVAL_TYPE(ev) != ')') { + add_expr(ev); + nargs++; + if(EVAL_TYPE(ev) == ')') break; + + if(EVAL_TYPE(ev) != ',') + EVAL_ERROR(ERR_BRACKET); + lex(ev); + } + lex(ev); + + // abs(x) - absolute value of x + if(!istrcmp(id, "abs")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, fabs(pop(ev))); + } + // ceil(x) - smallest integer greater than x + else if(!istrcmp(id, "ceil")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, ceil(pop(ev))); + } + // floor(x) - largest integer smaller than x + else if(!istrcmp(id, "floor")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, floor(pop(ev))); + } + // sin(x) - sine of x, in radians + else if(!istrcmp(id, "sin")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, sin(pop(ev))); + } + // asin(x) - arcsine of x, in radians + else if(!istrcmp(id, "asin")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, asin(pop(ev))); + } + // cos(x) - cosine of x, in radians + else if(!istrcmp(id, "cos")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, cos(pop(ev))); + } + // acos(x) - arccosine of x, in radians + else if(!istrcmp(id, "acos")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, acos(pop(ev))); + } + // tan(x) - tangent of x, in radians + else if(!istrcmp(id, "tan")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, tan(pop(ev))); + } + // atan(x) - arctangent of x, in radians + else if(!istrcmp(id, "atan")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, atan(pop(ev))); + } + // atan(y,x) - arctangent of y/x, in radians. + else if(!istrcmp(id, "atan2")) { + double a, b; + if(nargs != 2) EVAL_ERROR(ERR_ARGS); + b = pop(ev); + a = pop(ev); + push(ev, atan2(a,b)); + } + // sinh(x) - hyperbolic sine of x, in radians + else if(!istrcmp(id, "sinh")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, sinh(pop(ev))); + } + // cosh(x) - hyperbolic cosine of x, in radians + else if(!istrcmp(id, "cosh")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, cosh(pop(ev))); + } + // tanh(x) - hyperbolic tangent of x, in radians + else if(!istrcmp(id, "tanh")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, tanh(pop(ev))); + } + // log(x) - natural logarithm of x + else if(!istrcmp(id, "log")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, log(pop(ev))); + } + // log10(x) - logarithm of x, base-10 + else if(!istrcmp(id, "log10")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, log10(pop(ev))); + } + // exp(x) - computes e^x + else if(!istrcmp(id, "exp")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, exp(pop(ev))); + } + // sqrt(x) - square root of x + else if(!istrcmp(id, "sqrt")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, sqrt(pop(ev))); + } + // rad(x) - converts x from degrees to radians + else if(!istrcmp(id, "rad")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, pop(ev)*EVAL_PI/180); + } + // deg(x) - converts x from radians to degrees + else if(!istrcmp(id, "deg")) { + if(nargs != 1) EVAL_ERROR(ERR_ARGS); + push(ev, pop(ev)*180/EVAL_PI); + } + // pow(x,y) - computes x^y + else if(!istrcmp(id, "pow")) { + double a, b; + if(nargs != 2) EVAL_ERROR(ERR_ARGS); + b = pop(ev); + a = pop(ev); + push(ev, pow(a,b)); + } + // hypot(x,y) - computes sqrt(x*x + y*y) + else if(!istrcmp(id, "hypot")) { + double a, b; + if(nargs != 2) EVAL_ERROR(ERR_ARGS); + b = pop(ev); + a = pop(ev); + push(ev, sqrt(a*a + b*b)); + } + else + EVAL_ERROR(ERR_FUNC); + } + } +} + +// + +#ifdef EVALDEMO +#include +int main() { + assert( eval("1+1") == 2 ); // common path + assert( eval("1+") != eval("1+") ); // check that errors return NAN + assert(~puts("Ok") ); +} +#endif +#line 0 // #define SQLITE_OMIT_LOAD_EXTENSION // #define SQLITE_CORE 1 // #define SQLITE_DEBUG 1 diff --git a/engine/v4k.c b/engine/v4k.c index 3aaceea..4060ac1 100644 --- a/engine/v4k.c +++ b/engine/v4k.c @@ -7756,7 +7756,7 @@ void font_color(const char *tag, uint32_t color) { if( f->initialized ) { glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_1D, f->texture_colors); - glTexSubImage1D(GL_TEXTURE_1D, 0, 0, FONT_MAX_COLORS, GL_BGRA, GL_UNSIGNED_BYTE, font_palette); + glTexSubImage1D(GL_TEXTURE_1D, 0, 0, FONT_MAX_COLORS, GL_RGBA, GL_UNSIGNED_BYTE, font_palette); } } } @@ -7954,7 +7954,7 @@ void font_face_from_mem(const char *tag, const void *ttf_bufferv, unsigned ttf_l glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // last chance to inspect the font atlases - if( flag("--debug-font-atlas") ) + if( flag("--font-debug") ) stbi_write_png(va("debug_font_atlas%d.png", index), f->width, f->height, 1, bitmap, 0); FREE(bitmap); @@ -8002,7 +8002,7 @@ void font_face_from_mem(const char *tag, const void *ttf_bufferv, unsigned ttf_l glGenTextures(1, &f->texture_colors); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_1D, f->texture_colors); - glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, FONT_MAX_COLORS, 0, GL_BGRA, GL_UNSIGNED_BYTE, font_palette); + glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, FONT_MAX_COLORS, 0, GL_RGBA, GL_UNSIGNED_BYTE, font_palette); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT); @@ -9318,9 +9318,6 @@ float ease_inout_bounce(float t) { return t < 0.5f ? 0.5f*ease_in_bounce(t*2) : float ease_inout_perlin(float t) { float t3=t*t*t,t4=t3*t,t5=t4*t; return 6*t5-15*t4+10*t3; } -float ease_ping_pong(float t, float(*fn1)(float), float(*fn2)(float)) { return t < 0.5 ? fn1(t*2) : fn2(1-(t-0.5)*2); } -float ease_pong_ping(float t, float(*fn1)(float), float(*fn2)(float)) { return 1 - ease_ping_pong(t,fn1,fn2); } - float ease(float t01, unsigned mode) { typedef float (*easing)(float); easing modes[] = { @@ -9365,6 +9362,10 @@ float ease(float t01, unsigned mode) { return modes[clampi(mode, 0, countof(modes))](clampf(t01,0,1)); } +float ease_pong(float t, unsigned fn) { return 1 - ease(t, fn); } +float ease_ping_pong(float t, unsigned fn1, unsigned fn2) { return t < 0.5 ? ease(t*2,fn1) : ease(1-(t-0.5)*2,fn2); } +float ease_pong_ping(float t, unsigned fn1, unsigned fn2) { return 1 - ease_ping_pong(t,fn1,fn2); } + // ---------------------------------------------------------------------------- float deg (float radians) { return radians / C_PI * 180.0f; } @@ -10159,6 +10160,14 @@ void printq( quat q ) { print_(&q.x,4,1); } void print33( float *m ) { print_(m,3,3); } void print34( float *m ) { print_(m,3,4); } void print44( float *m ) { print_(m,4,4); } + +// ----------- + +AUTORUN { + STRUCT( vec3, float, x ); + STRUCT( vec3, float, y ); + STRUCT( vec3, float, z, "Up" ); +} #line 0 #line 1 "engine/split/v4k_memory.c" @@ -11243,6 +11252,51 @@ void network_rpc_send(unsigned id, const char *cmdline) { #line 0 #line 1 "engine/split/v4k_pack.c" +// ----------------------------------------------------------------------------- +// semantic versioning in a single byte (octal) +// - rlyeh, public domain. +// +// - single octal byte that represents semantic versioning (major.minor.patch). +// - allowed range [0000..0377] ( <-> [0..255] decimal ) +// - comparison checks only major.minor tuple as per convention. + +int semver( int major, int minor, int patch ) { + return SEMVER(major, minor, patch); +} +int semvercmp( int v1, int v2 ) { + return SEMVERCMP(v1, v2); +} + +#if 0 +AUTORUN { + for( int i= 0; i <= 255; ++i) printf(SEMVERFMT ",", i); + puts(""); + + printf(SEMVERFMT "\n", semver(3,7,7)); + printf(SEMVERFMT "\n", semver(2,7,7)); + printf(SEMVERFMT "\n", semver(1,7,7)); + printf(SEMVERFMT "\n", semver(0,7,7)); + + printf(SEMVERFMT "\n", semver(3,7,1)); + printf(SEMVERFMT "\n", semver(2,5,3)); + printf(SEMVERFMT "\n", semver(1,3,5)); + printf(SEMVERFMT "\n", semver(0,1,7)); + + assert( semvercmp( 0357, 0300 ) > 0 ); + assert( semvercmp( 0277, 0300 ) < 0 ); + assert( semvercmp( 0277, 0200 ) > 0 ); + assert( semvercmp( 0277, 0100 ) < 0 ); + assert( semvercmp( 0076, 0070 ) == 0 ); + assert( semvercmp( 0076, 0077 ) == 0 ); + assert( semvercmp( 0176, 0170 ) == 0 ); + assert( semvercmp( 0176, 0177 ) == 0 ); + assert( semvercmp( 0276, 0270 ) == 0 ); + assert( semvercmp( 0276, 0277 ) == 0 ); + assert( semvercmp( 0376, 0370 ) == 0 ); + assert( semvercmp( 0376, 0377 ) == 0 ); +} +#endif + // ----------------------------------------------------------------------------- // compile-time fourcc, eightcc @@ -11270,24 +11324,18 @@ char *cc8str(uint64_t x) { // ---------------------------------------------------------------------------- // float conversion (text) -vec2 atof2(const char *s) { - vec2 v = {0}; - sscanf(s, "%f,%f", &v.x, &v.y); - return v; +char* itoa1(int v) { + return va("%d", v); } -vec3 atof3(const char *s) { - vec3 v = {0}; - sscanf(s, "%f,%f,%f", &v.x, &v.y, &v.z); - return v; +char* itoa2(vec2i v) { + return va("%d,%d", v.x,v.y); } -vec4 atof4(const char *s) { - vec4 v = {0}; - sscanf(s, "%f,%f,%f,%f", &v.x, &v.y, &v.z, &v.w); - return v; +char* itoa3(vec3i v) { + return va("%d,%d,%d", v.x,v.y,v.z); } -char* ftoa(float f) { - return va("%f", f); +char* ftoa1(float v) { + return va("%f", v); } char* ftoa2(vec2 v) { return va("%f,%f", v.x, v.y); @@ -11299,6 +11347,51 @@ char* ftoa4(vec4 v) { return va("%f,%f,%f,%f", v.x, v.y, v.z, v.w); } +float atof1(const char *s) { + char buf[64]; + return sscanf(s, "%64[^]\r\n,}]", buf) == 1 ? (float)eval(buf) : (float)NAN; +} +vec2 atof2(const char *s) { + vec2 v = { 0 }; + char buf1[64],buf2[64]; + int num = sscanf(s, "%64[^]\r\n,}],%64[^]\r\n,}]", buf1, buf2); + if( num > 0 ) v.x = eval(buf1); + if( num > 1 ) v.y = eval(buf2); + return v; +} +vec3 atof3(const char *s) { + vec3 v = {0}; + char buf1[64],buf2[64],buf3[64]; + int num = sscanf(s, "%64[^]\r\n,}],%64[^]\r\n,}],%64[^]\r\n,}]", buf1, buf2, buf3); + if( num > 0 ) v.x = eval(buf1); + if( num > 1 ) v.y = eval(buf2); + if( num > 2 ) v.z = eval(buf3); + return v; +} +vec4 atof4(const char *s) { + vec4 v = {0}; + char buf1[64],buf2[64],buf3[64],buf4[64]; + int num = sscanf(s, "%64[^]\r\n,}],%64[^]\r\n,}],%64[^]\r\n,}],%64[^]\r\n,}]", buf1, buf2, buf3, buf4); + if( num > 0 ) v.x = eval(buf1); + if( num > 1 ) v.y = eval(buf2); + if( num > 2 ) v.z = eval(buf3); + if( num > 3 ) v.w = eval(buf4); + return v; +} + +// @todo: expand this to proper int parsers +int atoi1(const char *s) { + return (int)atof1(s); +} +vec2i atoi2(const char *s) { + vec2 v = atof2(s); + return vec2i( v.x, v.y ); +} +vec3i atoi3(const char *s) { + vec3 v = atof3(s); + return vec3i( v.x, v.y, v.z ); +} + // endianness ----------------------------------------------------------------- // - rlyeh, public domain @@ -12859,10 +12952,10 @@ void *member_findptr(void *obj, const char *T, const char *M) { M = symbol(M); return (char*)obj + member_find(T,M).sz; } -array(reflect_t) members_find(const char *T) { +array(reflect_t)* members_find(const char *T) { reflect_init(); T = symbol(T); - return *map_find(members, intern(T)); + return map_find(members, intern(T)); } @@ -13478,13 +13571,13 @@ void shader_colormap(const char *name, colormap_t c ) { // colors unsigned rgba( uint8_t r, uint8_t g, uint8_t b, uint8_t a ) { - return (unsigned)r << 24 | g << 16 | b << 8 | a; + return (unsigned)a << 24 | b << 16 | g << 8 | r; } unsigned bgra( uint8_t b, uint8_t g, uint8_t r, uint8_t a ) { return rgba(r,g,b,a); } -float alpha( unsigned rgba ) { - return ( rgba >> 24 ) / 255.f; +unsigned alpha( unsigned rgba ) { + return rgba >> 24; } unsigned rgbaf(float r, float g, float b, float a) { @@ -19847,7 +19940,7 @@ void trap_on_abort(int signal) { exit(-1); } void trap_on_debug(int signal) { - breakpoint("Error: unexpected signal"); + alert("Error: unexpected signal"), breakpoint(); fprintf(stderr, "Error: unexpected signal %s (%d)\n%s\n", trap_name(signal), signal, callstack(16)); exit(-1); } @@ -20117,7 +20210,7 @@ void tty_color(unsigned color) { } #endif if( color ) { - // if( color == RED ) breakpoint("break on RED"); // debug + // if( color == RED ) alert("break on error message (RED)"), breakpoint(); // debug unsigned r = (color >> 16) & 255; unsigned g = (color >> 8) & 255; unsigned b = (color >> 0) & 255; @@ -20236,6 +20329,9 @@ static void debugbreak(void) { // break if debugger present #endif void alert(const char *message) { // @todo: move to app_, besides die() + window_visible(false); + message = message[0] == '!' ? (const char*)va("%s\n%s", message+1, callstack(+48)) : message; + #if is(win32) MessageBoxA(0, message, 0,0); #elif is(ems) @@ -20246,18 +20342,12 @@ void alert(const char *message) { // @todo: move to app_, besides die() #elif is(osx) system(va("osascript -e 'display alert \"Alert\" message \"%s\"'", message)); #endif -} -void breakpoint(const char *reason) { - window_visible(false); - if( reason ) { - const char *fulltext = reason[0] == '!' ? va("%s\n%s", reason+1, callstack(+48)) : reason; - PRINTF("%s", fulltext); - - (alert)(fulltext); - } - debugbreak(); window_visible(true); + } + +void breakpoint() { + debugbreak(); } bool has_debugger() { @@ -20320,7 +20410,9 @@ int (PANIC)(const char *error, const char *file, int line) { tty_color(0); - breakpoint(error); + alert(error); + breakpoint(); + exit(-line); return 1; } @@ -20472,11 +20564,13 @@ const char* app_savefile() { // ---------------------------------------------------------------------------- // tests -static __thread int test_oks, test_errs, test_once; -static void test_exit(void) { fprintf(stderr, "%d/%d tests passed\n", test_oks, test_oks+test_errs); } +static __thread int test_oks, test_errors, test_once; +static void test_exit(void) { fprintf(stderr, "%d/%d tests passed\n", test_oks, test_oks+test_errors); } int (test)(const char *file, int line, const char *expr, bool result) { + static int breakon = -1; if(breakon<0) breakon = optioni("--test-break", 0); + if( breakon == (test_oks+test_errors+1) ) alert("user requested to break on this test"), breakpoint(); test_once = test_once || !(atexit)(test_exit); - test_oks += result, test_errs += !result; + test_oks += result, test_errors += !result; return (result || (tty_color(RED), fprintf(stderr, "(Test `%s` failed %s:%d)\n", expr, file, line), tty_color(0), 0) ); } #line 0 @@ -20659,7 +20753,6 @@ bool id_valid(uintptr_t id) { #define UI_FONT_ENUM(carlito,b612) b612 // carlito -#define UI_FONT_ICONS "MaterialIconsSharp-Regular.otf" // "MaterialIconsOutlined-Regular.otf" "MaterialIcons-Regular.ttf" // #define UI_FONT_REGULAR UI_FONT_ENUM("Carlito", "B612") "-Regular.ttf" #define UI_FONT_HEADING UI_FONT_ENUM("Carlito", "B612") "-BoldItalic.ttf" #define UI_FONT_TERMINAL UI_FONT_ENUM("Inconsolata", "B612Mono") "-Regular.ttf" @@ -20730,12 +20823,16 @@ static void nk_config_custom_fonts() { } // ...with icons embedded on it. - - for( char *data = vfs_load(UI_FONT_ICONS, &datalen); data; data = 0 ) { - static const nk_rune icon_range[] = {UI_ICON_MIN, UI_ICON_MED /*MAX*/, 0}; - + static struct icon_font { + const char *file; nk_rune range[3]; + } icons[] = { + {"MaterialIconsSharp-Regular.otf", {UI_ICON_MIN, UI_ICON_MED /*MAX*/, 0}}, // "MaterialIconsOutlined-Regular.otf" "MaterialIcons-Regular.ttf" + {"materialdesignicons-webfont.ttf", {0xF68C /*ICON_MIN_MDI*/, 0xF1C80/*ICON_MAX_MDI*/, 0}}, + }; + for( int f = 0; f < countof(icons); ++f ) + for( char *data = vfs_load(icons[f].file, &datalen); data; data = 0 ) { struct nk_font_config cfg = nk_font_config(UI_ICON_FONTSIZE); - cfg.range = icon_range; // nk_font_default_glyph_ranges(); + cfg.range = icons[f].range; // nk_font_default_glyph_ranges(); cfg.merge_mode = 1; cfg.spacing.x += UI_ICON_SPACING_X; @@ -23680,7 +23777,8 @@ void glNewFrame() { } bool window_create_from_handle(void *handle, float scale, unsigned flags) { - ifdef(debug, if( flag("--tests") ) exit(0)); + // abort run if any test suite failed in unit-test mode + ifdef(debug, if( flag("--test-only") ) exit( test_errors ? -test_errors : 0 )); glfw_init(); v4k_init(); @@ -24692,50 +24790,807 @@ int window_has_maximize() { #line 0 #line 1 "engine/split/v4k_obj.c" -// ----------------------------------------------------------------------------- -// semantic versioning in a single byte (octal) +// C objects framework // - rlyeh, public domain. - // -// - single octal byte that represents semantic versioning (major.minor.patch). -// - allowed range [0000..0377] ( <-> [0..255] decimal ) -// - comparison checks only major.minor tuple as per convention. -int semver( int major, int minor, int patch ) { - return SEMVER(major, minor, patch); +// --- implement new vtables + +obj_vtable(ctor, void, {} ); +obj_vtable(dtor, void, {} ); + +obj_vtable_null(save, char* ); +obj_vtable_null(load, bool ); +obj_vtable_null(test, int ); + +obj_vtable_null(init, int ); +obj_vtable_null(quit, int ); +obj_vtable_null(tick, int ); +obj_vtable_null(draw, int ); + +obj_vtable_null(lerp, int ); +obj_vtable_null(edit, int ); // OSC cmds: argc,argv "undo","redo","cut","copy","paste","edit","view","menu" + +// ---------------------------------------------------------------------------- + +const char *OBJTYPES[256] = { 0 }; // = { REPEAT256("") }; + +// ---------------------------------------------------------------------------- +// heap/stack ctor/dtor + +void *obj_malloc(unsigned sz) { + //sz = sizeof(obj) + sz + sizeof(array(obj*))); // useful? + obj *ptr = CALLOC(1, sz); + OBJ_CTOR_HDR(ptr,1,intern("obj"),sz,OBJTYPE_obj); + return ptr; } -int semvercmp( int v1, int v2 ) { - return SEMVERCMP(v1, v2); +void *obj_free(void *o) { + if( !((obj*)o)->objrefs ) { + obj_dtor(o); + //obj_zero(o); + if( ((obj*)o)->objheap ) { + FREE(o); + } + return 0; +} + return o; // cannot destroy: object is still referenced } -#if 0 -AUTORUN { - for( int i= 0; i <= 255; ++i) printf(SEMVERFMT ",", i); - puts(""); +// ---------------------------------------------------------------------------- +// core - printf(SEMVERFMT "\n", semver(3,7,7)); - printf(SEMVERFMT "\n", semver(2,7,7)); - printf(SEMVERFMT "\n", semver(1,7,7)); - printf(SEMVERFMT "\n", semver(0,7,7)); - - printf(SEMVERFMT "\n", semver(3,7,1)); - printf(SEMVERFMT "\n", semver(2,5,3)); - printf(SEMVERFMT "\n", semver(1,3,5)); - printf(SEMVERFMT "\n", semver(0,1,7)); - - assert( semvercmp( 0357, 0300 ) > 0 ); - assert( semvercmp( 0277, 0300 ) < 0 ); - assert( semvercmp( 0277, 0200 ) > 0 ); - assert( semvercmp( 0277, 0100 ) < 0 ); - assert( semvercmp( 0076, 0070 ) == 0 ); - assert( semvercmp( 0076, 0077 ) == 0 ); - assert( semvercmp( 0176, 0170 ) == 0 ); - assert( semvercmp( 0176, 0177 ) == 0 ); - assert( semvercmp( 0276, 0270 ) == 0 ); - assert( semvercmp( 0276, 0277 ) == 0 ); - assert( semvercmp( 0376, 0370 ) == 0 ); - assert( semvercmp( 0376, 0377 ) == 0 ); +uintptr_t obj_header(const void *o) { + return ((obj*)o)->objheader; } +uintptr_t obj_id(const void *o) { + return ((obj*)o)->objid; +} +unsigned obj_typeid(const void *o) { + return ((obj*)o)->objtype; +} +const char *obj_type(const void *o) { + return OBJTYPES[ (((obj*)o)->objtype) ]; +} +const char *obj_name(const void *o) { + return quark(((obj*)o)->objnameid); +} +int obj_sizeof(const void *o) { + return (int)( ((const obj*)o)->objsizew << OBJ_MIN_PRAGMAPACK_BITS ); +} +int obj_size(const void *o) { // size of all members together in struct. may include padding bytes. + static int obj_sizes[256] = {0}; + unsigned objtypeid = ((obj*)o)->objtype; + if( objtypeid > 1 && !obj_sizes[objtypeid] ) { // check reflection for a more accurate objsize (without padding bits) + reflect_init(); + array(reflect_t) *found = map_find(members, intern(obj_type(o))); + if(!found) + obj_sizes[objtypeid] = obj_sizeof(o) - sizeof(obj); // @fixme: -= sizeof(entity); + else + for each_array_ptr(*found, reflect_t, it) + obj_sizes[objtypeid] += it->bytes; + } + return obj_sizes[objtypeid]; +} +char *obj_data(void *o) { // pointer to the first member in struct + return (char*)o + sizeof(obj); +} +const char *obj_datac(const void *o) { // const pointer to the first struct member + return (const char*)o + sizeof(obj); +} +void* obj_payload(const void *o) { // pointer right after last member in struct + return (char*)o + (((obj*)o)->objsizew<objrefs; + ++o->objrefs; + assert( num < o->objrefs && "Object referenced too many times"); + //++global_ref_count; + return o; +} +void *obj_unref(void *oo) { + obj* o = (obj*)oo; + if( o->objrefs ) --o->objrefs; + if( o->objrefs ) return o; + obj_free(o); + //--global_ref_count; + return 0; +} + +// ---------------------------------------------------------------------------- +// scene tree + +array(obj*)* obj_children(const void *o) { + array(obj*) *c = obj_payload(o); + if(!(*c)) array_push((*c), NULL); // default parenting: none. @todo: optimize & move this at construction time + return c; +} +obj* obj_parent(const void *o) { + array(obj*) *c = obj_children(o); + return (*c) ? 0[*c] : NULL; +} +obj* obj_root(const void *o) { + while( obj_parent(o) ) o = obj_parent(o); + return (obj*)o; +} +array(obj*)* obj_siblings(const void *o) { + return obj_children(obj_parent(o)); +} + +static +obj* obj_reparent(obj *o, const void *p) { + array(obj*) *c = obj_children(o); + 0[*c] = (void*)p; + return o; +} + +obj* obj_detach(void *c) { + obj *p = obj_parent(c); + if( p ) { + uintptr_t id = obj_id(c); + + array(obj*) *oo = obj_children(p); + for( int i = 1, end = array_count(*oo); i < end; ++i) { + obj *v = (*oo)[i]; + { + if( obj_id(v) == id ) { + obj_reparent(c, 0); + return c; + } + } + } + } + return 0; +} +obj* obj_attach(void *o, void *c) { + // reattach + obj_detach(c); + obj_reparent(c, o); + // insert into children + array(obj*) *p = obj_children(o); + array_push(*p, c); + return o; +} + +int obj_dumptree(const void *o) { + static int tabs = 0; + printf("%*s" "+- %s\n", tabs++, "", obj_name(o)); + for each_objchild(o, obj, v) { + obj_dumptree(v); + } + --tabs; + return 0; +} + +static +void test_obj_scene() { + obj *r = obj_new_ext(obj, "root"); // root + obj *c1 = obj_new_ext(obj, "child1"); // child1 + obj *c2 = obj_new_ext(obj, "child2"); // child2 + obj *gc1 = obj_new_ext(obj, "grandchild1"); // grandchild1 + obj *gc2 = obj_new_ext(obj, "grandchild2"); // grandchild2 + obj *gc3 = obj_new_ext(obj, "grandchild3"); // grandchild3 + + test( !obj_parent(r) ); + test( !obj_parent(c1) ); + test( !obj_parent(c2) ); + test( !obj_parent(gc1) ); + test( !obj_parent(gc2) ); + test( !obj_parent(gc3) ); + test( obj_root(r) == r ); + test( obj_root(c1) == c1 ); + test( obj_root(c2) == c2 ); + test( obj_root(gc1) == gc1 ); + test( obj_root(gc2) == gc2 ); + test( obj_root(gc3) == gc3 ); + + // r + obj_attach(r, c1); // +- c1 + obj_attach(c1, gc1); // +- gc1 + obj_attach(r, c2); // +- c2 + obj_attach(c2, gc2); // +- gc2 + obj_attach(c2, gc3); // +- gc3 + + // obj_dumptree(r); + // puts("---"); + + test( obj_parent(r) == 0 ); + test( obj_parent(c1) == r ); + test( obj_parent(c2) == r ); + test( obj_parent(gc1) == c1 ); + test( obj_parent(gc2) == c2 ); + test( obj_parent(gc3) == c2 ); + + test( obj_root(r) == r ); + test( obj_root(c1) == r ); + test( obj_root(c2) == r ); + test( obj_root(gc1) == r ); + test( obj_root(gc2) == r ); + test( obj_root(gc3) == r ); + + for each_objchild(r, obj, o) test( o == c1 || o == c2 ); + for each_objchild(c1, obj, o) test( o == gc1 ); + for each_objchild(c2, obj, o) test( o == gc2 || o == gc3 ); + + obj_detach(c1); + test( !obj_parent(c1) ); + for each_objchild(r, obj, o) test( o != c1 ); + for each_objchild(c1, obj, o) test( o == gc1 ); + + obj_detach(c2); + test( !obj_parent(c2) ); + for each_objchild(r, obj, o) test( o != c2 ); + for each_objchild(c2, obj, o) test( o == gc2 || o == gc3 ); +} + +// ---------------------------------------------------------------------------- +// metadata + +static map(int,int) oms; +static thread_mutex_t *oms_lock; +const char *obj_metaset(const void *o, const char *key, const char *value) { + do_threadlock(oms_lock) { + if(!oms) map_init_int(oms); + int *q = map_find_or_add(oms, intern(va("%llu-%s",obj_id((obj*)o),key)), 0); + if(!*q && !value[0]) {} else *q = intern(value); + return quark(*q); + } + return 0; // unreachable +} +const char* obj_metaget(const void *o, const char *key) { + do_threadlock(oms_lock) { + if(!oms) map_init_int(oms); + int *q = map_find_or_add(oms, intern(va("%llu-%s",obj_id((obj*)o),key)), 0); + return quark(*q); + } + return 0; // unreachable +} + +static +void test_obj_metadatas( void *o1 ) { + obj *o = (obj *)o1; + test( !strcmp("", obj_metaget(o, "has_passed_test")) ); + test( !strcmp("yes", obj_metaset(o, "has_passed_test", "yes")) ); + test( !strcmp("yes", obj_metaget(o, "has_passed_test")) ); +} + +// ---------------------------------------------------------------------------- +// stl + +void* obj_swap(void *dst, void *src) { // @testme + int len = obj_size(dst); + char *buffer = ALLOCA(len); + memcpy(buffer, obj_datac(dst), len); + memcpy(obj_data(dst), obj_datac(src), len); + memcpy(obj_data(src), buffer, len); + return dst; +} + +void* obj_copy_fast(void *dst, const void *src) { + // note: prefer obj_copy() as it should handle pointers and guids as well + return memcpy(obj_data(dst), obj_datac(src), obj_size(dst)); +} +void* obj_copy(void *dst, const void *src) { // @testme + // @todo: use obj_copy_fast() silently if the object does not contain any pointers/guids + return obj_loadini(dst, obj_saveini(src)); + // return obj_load(dst, obj_save(src)); + // return obj_loadbin(dst, obj_savebin(src)); + // return obj_loadini(dst, obj_saveini(src)); + // return obj_loadjson(dst, obj_savejson(src)); + // return obj_loadmpack(dst, obj_savempack(src)); +} + +int obj_comp_fast(const void *a, const void *b) { + // note: prefer obj_comp() as it should handle pointers and guids as well + return memcmp(obj_datac(a), obj_datac(b), obj_size(a)); +} +int obj_comp(const void *a, const void *b) { + // @todo: use obj_comp_fast() silently if the object does not contain any pointers/guids + return strcmp(obj_saveini(a),obj_saveini(b)); +} +int obj_lesser(const void *a, const void *b) { + return obj_comp(a,b) < 0; +} +int obj_greater(const void *a, const void *b) { + return obj_comp(a,b) > 0; +} +int obj_equal(const void *a, const void *b) { + return obj_comp(a,b) == 0; +} + +uint64_t obj_hash(const void *o) { + return hash_bin(obj_datac(o), obj_size(o)); +} + +static +void test_obj_similarity(void *o1, void *o2) { + obj *b = (obj*)o1; + obj *c = (obj*)o2; + test( 0 == strcmp(obj_name(b),obj_name(c)) ); + test( 0 == strcmp(obj_type(b),obj_type(c)) ); +} +static +void test_obj_equality(void *o1, void *o2) { + obj *b = (obj*)o1; + obj *c = (obj*)o2; + test_obj_similarity(b, c); + test( obj_size(b) == obj_size(c) ); + test( obj_hash(b) == obj_hash(c) ); + test( 0 == obj_comp(b,c) ); + test( obj_equal(b,c) ); + test( !obj_lesser(b,c) ); + test( !obj_greater(b,c) ); +} +static +void test_obj_exact(void *o1, void *o2) { + obj *b = (obj*)o1; + obj *c = (obj*)o2; + test_obj_equality(b, c); + test( obj_header(b) == obj_header(c) ); + test( 0 == memcmp(b, c, obj_sizeof(b)) ); +} + +// ---------------------------------------------------------------------------- +// debug + +bool obj_hexdump(const void *oo) { + const obj *o = (const obj *)oo; + int header = 1 * sizeof(obj); + printf("; name[%s] type[%s] id[%d..%d] unused[%08x] sizeof[%02d] %llx\n", + obj_name(o), obj_type(o), + (int)o->objid>>16, (int)o->objid&0xffff, (int)o->objunused, + obj_sizeof(o), o->objheader); + return hexdump(obj_datac(o) - header, obj_size(o) + header), 1; +} +int obj_print(const void *o) { + char *sav = obj_saveini(o); // obj_savejson(o) + return puts(sav); +} +static char *obj_tempname = 0; +static FILE *obj_filelog = 0; +int (obj_printf)(const void *o, const char *text) { + if( !obj_tempname ) { + obj_tempname = stringf("%s.log", app_name()); + unlink(obj_tempname); + obj_filelog = fopen(obj_tempname, "w+b"); + if( obj_filelog ) fseek(obj_filelog, 0L, SEEK_SET); + } + int rc = 0; + for( char *end; (end = strchr(text, '\n')) != NULL; ) { + rc |= fprintf(obj_filelog, "[%p] %.*s\n", o, (int)(end - text), text ); + text = end + 1; + } + if( text[0] ) rc |= fprintf(obj_filelog, "[%p] %s\n", o, text); + return rc; +} +int obj_console(const void *o) { // obj_output() ? + if( obj_filelog ) fflush(obj_filelog); + return obj_tempname && !system(va(ifdef(win32,"type \"%s\" | find \"[%p]\"", "cat %s | grep \"[%p]\""), obj_tempname, o)); +} + +static +void test_obj_console(void *o1) { + obj *o = (obj *)o1; + + obj_printf(o, "this is [%s], line 1\n", obj_name(o)); + obj_printf(NULL, "this line does not belong to any object\n"); + obj_printf(o, "this is [%s], line 2\n", obj_name(o)); + obj_console(o); +} + +// ---------------------------------------------------------------------------- +// serialization + +const char *p2s(const char *type, void *p) { + // @todo: p2s(int interned_type, void *p) + /**/ if( !strcmp(type, "int") ) return itoa1(*(int*)p); + else if( !strcmp(type, "unsigned") ) return itoa1(*(unsigned*)p); + else if( !strcmp(type, "float") ) return ftoa1(*(float*)p); + else if( !strcmp(type, "double") ) return ftoa1(*(double*)p); + else if( !strcmp(type, "uintptr_t") ) return va("%08llx", *(uintptr_t*)p); + else if( !strcmp(type, "vec2i") ) return itoa2(*(vec2i*)p); + else if( !strcmp(type, "vec3i") ) return itoa3(*(vec3i*)p); + else if( !strcmp(type, "vec2") ) return ftoa2(*(vec2*)p); + else if( !strcmp(type, "vec3") ) return ftoa3(*(vec3*)p); + else if( !strcmp(type, "vec4") ) return ftoa4(*(vec4*)p); + else if( !strcmp(type, "char*") || !strcmp(type, "string") ) return va("%s", *(char**)p); + // @todo: if strchr('*') assume obj, if reflected save guid: obj_id(); + return tty_color(YELLOW), printf("p2s: cannot serialize `%s` type\n", type), tty_color(0), ""; +} +bool s2p(void *P, const char *type, const char *str) { + int i; unsigned u; float f; double g; char *s = 0; uintptr_t p; + vec2 v2; vec3 v3; vec4 v4; vec2i v2i; vec3i v3i; + /**/ if( !strcmp(type, "int") ) return !!memcpy(P, (i = atoi1(str), &i), sizeof(i)); + else if( !strcmp(type, "unsigned") ) return !!memcpy(P, (u = atoi1(str), &u), sizeof(u)); + else if( !strcmp(type, "vec2i") ) return !!memcpy(P, (v2i = atoi2(str), &v2i), sizeof(v2i)); + else if( !strcmp(type, "vec3i") ) return !!memcpy(P, (v3i = atoi3(str), &v3i), sizeof(v3i)); + else if( !strcmp(type, "float") ) return !!memcpy(P, (f = atof1(str), &f), sizeof(f)); + else if( !strcmp(type, "double") ) return !!memcpy(P, (g = atof1(str), &g), sizeof(g)); + else if( !strcmp(type, "vec2") ) return !!memcpy(P, (v2 = atof2(str), &v2), sizeof(v2)); + else if( !strcmp(type, "vec3") ) return !!memcpy(P, (v3 = atof3(str), &v3), sizeof(v3)); + else if( !strcmp(type, "vec4") ) return !!memcpy(P, (v4 = atof4(str), &v4), sizeof(v4)); + else if( !strcmp(type, "uintptr_t") ) return !!memcpy(P, (p = strtol(str, NULL, 16), &p), sizeof(p)); + else if( !strcmp(type, "char*") || !strcmp(type, "string") ) { + char substring[128] = {0}; + sscanf(str, "%[^\r\n]", substring); + + strcatf(&s, "%s", substring); + + *(uintptr_t*)(P) = (uintptr_t)s; + return 1; + } + // @todo: if strchr('*') assume obj, if reflected load guid: obj_id(); + return tty_color(YELLOW), printf("s2p: cannot deserialize `%s` type\n", type), tty_color(0), 0; +} + +char *obj_saveini(const void *o) { // @testme + char *out = 0; + const char *T = obj_type(o); + strcatf(&out, "[%s] ; v100\n", T); + for each_member(T,R) { + const char *sav = p2s(R->type,(char*)(o)+R->sz); + if(!sav) return FREE(out), NULL; + strcatf(&out,"%s.%s=%s\n", R->type,R->name,sav ); + } + char *cpy = va("%s", out); + FREE(out); + return cpy; +} +obj *obj_mergeini(void *o, const char *ini) { // @testme + const char *sqr = strchr(ini, '['); + if( !sqr ) return 0; + ini = sqr+1; + + char T[64] = {0}; + if( sscanf(ini, "%64[^]]", &T) != 1 ) return 0; // @todo: parse version as well + ini += strlen(T); + + for each_member(T,R) { + char *lookup = va("\n%s.%s=", R->type,R->name), *found = 0; + + // type needed? /* + if(!found) { found = strstr(ini, lookup); if (found) found += strlen(lookup); } + if(!found) { *lookup = '\r'; } + if(!found) { found = strstr(ini, lookup); if (found) found += strlen(lookup); } + // */ + + if(!found) lookup = va("\n%s=", R->name); + + if(!found) { found = strstr(ini, lookup); if (found) found += strlen(lookup); } + if(!found) { *lookup = '\r'; } + if(!found) { found = strstr(ini, lookup); if (found) found += strlen(lookup); } + + if( found) { + if(!s2p((char*)(o)+R->sz, R->type, found)) + return 0; + } + } + return o; +} +obj *obj_loadini(void *o, const char *ini) { // @testme + return obj_mergeini(obj_zero(o), ini); +} + +char *obj_savejson(const void *o) { + char *j = 0; + const char *T = obj_type(o); + for each_member(T,R) { + const char *sav = p2s(R->type,(char*)(o)+R->sz); + if(!sav) return FREE(j), NULL; + char is_string = !strcmp(R->type,"char*") || !strcmp(R->type,"string"); + strcatf(&j," %s: %s%s%s,\n", R->name,is_string?"\"":"",sav,is_string?"\"":"" ); + } + char *out = va("%s: { // v100\n%s}\n", T,j); + FREE(j); +#if is(debug) + json5 root = { 0 }; + char *error = json5_parse(&root, va("%s", out), 0); + assert( !error ); + json5_free(&root); #endif + return out; +} +obj *obj_mergejson(void *o, const char *json) { + // @fixme: va() call below could be optimized out since we could figure it out if json was internally provided (via va or strdup), or user-provided + json5 root = { 0 }; + char *error = json5_parse(&root, va("%s", json), 0); // @todo: parse version comment + if( !error && root.type == JSON5_OBJECT && root.count == 1 ) { + json5 *n = &root.nodes[0]; + char *T = n->name; + for each_member(T,R) { + for( int i = 0; i < n->count; ++i ) { + if( !strcmp(R->name, n->nodes[i].name) ) { + void *p = (char*)o + R->sz; + /**/ if( n->nodes[i].type == JSON5_UNDEFINED ) {} + else if( n->nodes[i].type == JSON5_NULL ) { + *(uintptr_t*)(p) = (uintptr_t)0; + } + else if( n->nodes[i].type == JSON5_BOOL ) { + *(bool*)p = n->nodes[i].boolean; + } + else if( n->nodes[i].type == JSON5_INTEGER ) { + if( strstr(R->type, "64" ) ) + *(int64_t*)p = n->nodes[i].integer; + else + *(int*)p = n->nodes[i].integer; + } + else if( n->nodes[i].type == JSON5_STRING ) { + char *s = 0; + strcatf(&s, "%s", n->nodes[i].string); + *(uintptr_t*)(p) = (uintptr_t)s; + } + else if( n->nodes[i].type == JSON5_REAL ) { + if( R->type[0] == 'f' ) + *(float*)(p) = n->nodes[i].real; + else + *(double*)(p) = n->nodes[i].real; + } + else if( n->nodes[i].type == JSON5_OBJECT ) {} + else if( n->nodes[i].type == JSON5_ARRAY ) {} + break; + } + } + } + } + json5_free(&root); + return error ? 0 : o; +} +obj *obj_loadjson(void *o, const char *json) { // @testme + return obj_mergejson(obj_zero(o), json); +} + +char *obj_savebin(const void *o) { // PACKMSG("ss", "entity_v1", quark(self->objnameid)); // = PACKMSG("p", obj_data(&b), (uint64_t)obj_size(&b)); + int len = cobs_bounds(obj_size(o)); + char *sav = va("%*.s", len, ""); + len = cobs_encode(obj_datac(o), obj_size(o), sav, len); + sav[len] = '\0'; + return sav; +} +obj *obj_mergebin(void *o, const char *sav) { // UNPACKMSG(sav, "p", obj_data(c), (uint64_t)obj_size(c)); + int outlen = cobs_decode(sav, strlen(sav), obj_data(o), obj_size(o)); + return outlen != obj_size(o) ? NULL : o; +} +obj *obj_loadbin(void *o, const char *sav) { + return obj_mergebin(obj_zero(o), sav); +} + +char *obj_savempack(const void *o) { // @todo + return ""; +} +obj *obj_mergempack(void *o, const char *sav) { // @todo + return 0; +} +obj *obj_loadmpack(void *o, const char *sav) { // @todo + return obj_mergempack(obj_zero(o), sav); +} + +static __thread array(char*) obj_stack; +int obj_push(const void *o) { + char *bin = STRDUP(obj_savebin(o)); + array_push(obj_stack, bin); + return 1; +} +int obj_pop(void *o) { + char *bin = *array_pop(obj_stack); + int rc = !!obj_loadbin(o, bin); + return FREE(bin), rc; +} + +static +void test_obj_serialization(void *o1, void *o2) { + obj* b = (obj*)o1; + obj* c = (obj*)o2; + + char *json = obj_savejson(b); // puts(json); + test( json[0] ); + char *ini = obj_saveini(b); // puts(ini); + test( ini[0] ); + char *bin = obj_savebin(b); // puts(bin); + test( bin[0] ); + + obj_push(c); + + test( obj_copy(c,b) ); + test( obj_comp(b,c) == 0 ) || obj_hexdump(b) & obj_hexdump(c); + + test( obj_zero(c) ); + test( obj_comp(c,b) != 0 ) || obj_hexdump(c); + test( obj_loadbin(c, bin) ); + test( obj_comp(c,b) == 0 ) || obj_hexdump(c) & obj_hexdump(b); + + test( obj_zero(c) ); + test( obj_comp(c,b) != 0 ) || obj_hexdump(c); + test( obj_loadini(c, ini) ); + test( obj_comp(c,b) == 0 ) || obj_hexdump(c) & obj_hexdump(b); + + test( obj_zero(c) ); + test( obj_comp(c,b) != 0 ) || obj_hexdump(c); + test( obj_loadjson(c, json) ); + test( obj_comp(c,b) == 0 ) || obj_hexdump(c) & obj_hexdump(b); + + obj_pop(c); + obj_hexdump(c); +} + +// ---------------------------------------------------------------------------- +// components + +bool obj_addcomponent(void *object, unsigned c, void *ptr) { + entity *e = (entity*)object; + e->cflags |= (3ULL << c); + e->c[c & (OBJCOMPONENTS_MAX-1)] = ptr; + return 1; +} +bool obj_hascomponent(void *object, unsigned c) { + entity *e = (entity*)object; + return !!(e->cflags & (3ULL << c)); +} +void* obj_getcomponent(void *object, unsigned c) { + entity *e = (entity*)object; + return e->c[c & (OBJCOMPONENTS_MAX-1)]; +} +bool obj_delcomponent(void *object, unsigned c) { + entity *e = (entity*)object; + e->cflags &= ~(3ULL << c); + e->c[c & (OBJCOMPONENTS_MAX-1)] = NULL; + return 1; +} +bool obj_usecomponent(void *object, unsigned c) { + entity *e = (entity*)object; + e->cflags |= (1ULL << c); + return 1; +} +bool obj_offcomponent(void *object, unsigned c) { + entity *e = (entity*)object; + e->cflags &= ~(1ULL << c); + return 0; +} + +char *entity_save(entity *self) { + char *sav = obj_saveini(self); + return sav; +} + +AUTORUN { + STRUCT(entity, uintptr_t, cflags); + +// struct { OBJHEADER union { struct { uintptr_t objenabled : 32, objflagged : 32; }; uintptr_t cflags; }; void* c[32]; }; + + obj_extend(entity, save); +} + +static +void test_obj_ecs() { + entity *e = obj_new(entity); + puts(obj_save(e)); + + for( int i = 0; i < 32; ++i) test(0 == obj_hascomponent(e, i)); + for( int i = 0; i < 32; ++i) test(1 == obj_addcomponent(e, i, NULL)); + for( int i = 0; i < 32; ++i) test(1 == obj_hascomponent(e, i)); + for( int i = 0; i < 32; ++i) test(1 == obj_delcomponent(e, i)); + for( int i = 0; i < 32; ++i) test(0 == obj_hascomponent(e, i)); +} + +// ---------------------------------------------------------------------------- +// reflection + +void *obj_clone(const void *src) { + obj *ptr = obj_malloc( sizeof(obj) + obj_size(src) + sizeof(array(obj*)) ); + ptr->objheader = ((const obj *)src)->objheader; + obj_loadini(ptr, obj_saveini(src)); + return ptr; +} + +void* obj_merge(void *dst, const void *src) { // @testme + char *bin = obj_savebin(src); + return obj_mergebin(dst, bin); +} + +void* obj_mutate(void **dst, const void *src) { +#if 0 + // mutate a class. ie, convert a given object class into a different one, + // while preserving the original metas, components and references as much as possible. + // @todo iterate per field + + if(!*dst_) return *dst_ = obj_clone(src); + + void *dst = *dst_; + dtor(dst); + + unsigned src_sz = obj_sizeof(src); + unsigned src_id = obj_id(src); + + void *dst_ptr = *((void**)dst - 1); + unsigned payload = (OBJPAYLOAD16(dst_ptr) & 255) | src_id << 8; + FREE( OBJUNBOX(dst_ptr) ); + *((void**)dst - 1) = OBJBOX( STRDUP( OBJUNBOX(*((void**)src - 1)) ), payload); + + void *base = (void*)((void**)dst - 1); + base = REALLOC(base, src_sz + sizeof(void*)); + *dst_ = (char*)base + sizeof(void*); + dst = (char*)base + sizeof(void*); + memcpy(dst, src, src_sz); + + ctor(dst); +#endif + return dst; +} + +void *obj_make(const char *str) { + const char *T; + const char *I = strchr(str, '['); // is_ini + const char *J = strchr(str, '{'); // is_json + if( !I && !J ) return 0; + else if( I && !J ) T = I; + else if( !I && J ) T = J; + else T = I < J ? I : J; + + char name[64] = {0}; + if( sscanf(T+1, T == I ? "%64[^]]" : "%64[^:=]", &name) != 1 ) return 0; + + int has_components = 0; // @todo: support entities too + + unsigned Tid = intern(name); + reflect_init(); + reflect_t *found = map_find(reflects, Tid); + if(!found) return 0; + + obj *ptr = CALLOC(1, found->sz + has_components * sizeof(array(obj*))); + void *ret = (T == I ? obj_mergeini : obj_mergejson)(ptr, str); + OBJTYPES[ found->objtype ] = found->name; + OBJ_CTOR_PTR(ptr,1,found->id,found->sz,found->objtype); + + return ptr; // returns partial construction as well. @todo: just return `ret` for a more strict built/failed policy +} #line 0 #line 1 "engine/split/v4k_ai.c" @@ -26095,9 +26950,6 @@ void v4k_quit(void) { void v4k_init() { do_once { - // abort run if any test suite failed - if( test_errs ) exit(-1); - // install signal handlers ifdef(debug, trap_install()); diff --git a/engine/v4k.h b/engine/v4k.h index 64d0920..02f0efa 100644 --- a/engine/v4k.h +++ b/engine/v4k.h @@ -267,7 +267,7 @@ extern "C" { #define conc4t(a,b) a##b ///- #define macro(name) concat(name, __LINE__) -#define unique(name) concat(concat(name, L##__LINE__), __COUNTER__) +#define unique(name) concat(concat(concat(name,concat(_L,__LINE__)),_),__COUNTER__) #define defer(begin,end) for(int macro(i) = ((begin), 0); !macro(i); macro(i) = ((end), 1)) #define scope(end) defer((void)0, end) #define benchmark for(double macro(i) = 1, macro(t) = (time_ss(),-time_ss()); macro(i); macro(t)+=time_ss(), macro(i)=0, printf("%.4fs %2.f%% (" FILELINE ")\n", macro(t), macro(t)*100/0.0166667 )) @@ -292,8 +292,8 @@ extern "C" { #define ASSERT(expr, ...) (void)0 #define ASSERT_ONCE(expr, ...) (void)0 #else -#define ASSERT(expr, ...) do { int fool_msvc[] = {0,}; if(!(expr)) { fool_msvc[0]++; breakpoint(va("!Expression failed: " #expr " " FILELINE "\n" __VA_ARGS__)); } } while(0) -#define ASSERT_ONCE(expr, ...) do { int fool_msvc[] = {0,}; if(!(expr)) { fool_msvc[0]++; static int seen = 0; if(!seen) seen = 1, breakpoint(va("!Expression failed: " #expr " " FILELINE "\n" __VA_ARGS__)); } } while(0) +#define ASSERT(expr, ...) do { int fool_msvc[] = {0,}; if(!(expr)) { fool_msvc[0]++; alert(va("!Expression failed: " #expr " " FILELINE "\n" __VA_ARGS__)), breakpoint(); } } while(0) +#define ASSERT_ONCE(expr, ...) do { int fool_msvc[] = {0,}; if(!(expr)) { fool_msvc[0]++; static int seen = 0; if(!seen) seen = 1, alert(va("!Expression failed: " #expr " " FILELINE "\n" __VA_ARGS__)), breakpoint(); } } while(0) #endif #define STATIC_ASSERT(EXPR) typedef struct { unsigned macro(static_assert_on_L) : !!(EXPR); } unique(static_assert_on_L) @@ -372,6 +372,7 @@ extern "C" { // note: based on code by Joe Lowe (public domain). // note: XIU for C initializers, XCU for C++ initializers, XTU for C deinitializers +#define AUTORUN AUTORUN_( unique(fn) ) #ifdef __cplusplus #define AUTORUN_(fn) \ static void fn(void); \ @@ -395,8 +396,6 @@ extern "C" { static void fn(void) #endif -#define AUTORUN AUTORUN_( concat(concat(concat(fn_L,__LINE__),_),__COUNTER__) ) - #if 0 // autorun demo void byebye(void) { puts("seen after main()"); } AUTORUN { puts("seen before main()"); } @@ -413,7 +412,7 @@ AUTORUN { puts("seen before main() too"); atexit( byebye ); } // ----------------------------------------------------------------------------- // visibility -// win32 users would need to -DAPI=IMPORT/EXPORT as needed when using/building V4K as DLL. +// win32 users would need to -DAPI=EXPORT/IMPORT as needed when building/using V4K as DLL. #define IMPORT ifdef(win32, ifdef(gcc, __attribute__ ((dllimport)), __declspec(dllimport))) #define EXPORT ifdef(win32, ifdef(gcc, __attribute__ ((dllexport)), __declspec(dllexport))) @@ -586,7 +585,7 @@ static __thread unsigned array_n_; #define array_vlen_(t) ( vlen(t) - 0 ) #define array_realloc_(t,n) ( (t) = array_cast(t) vrealloc((t), ((n)+0) * sizeof(0[t])) ) #define array_free(t) array_clear(t) -#else // new: with reserve support (bugs?) +#else // new: with reserve support (@todo: check for bugs?) #define array_reserve(t, n) ( array_realloc_((t),(n)), array_clear(t) ) #define array_clear(t) ( array_realloc_((t),0) ) // -1 #define array_vlen_(t) ( vlen(t) - sizeof(0[t]) ) // -1 @@ -633,7 +632,7 @@ static __thread unsigned array_n_; memcpy( (t), src, array_count(src) * sizeof(0[t])); \ } while(0) -#define array_erase_fast(t, i) do { /*may alter ordering*/ \ +#define array_erase_fast(t, i) do { /*alters ordering*/ \ memcpy( &(t)[i], &(t)[array_count(t) - 1], sizeof(0[t])); \ array_pop(t); \ } while(0) @@ -955,9 +954,9 @@ API void (map_clear)(map* m); // Credits: @ands+@krig+@vurtun (PD), @datenwolf (WTFPL2), @evanw+@barerose (CC0), @sgorsten (Unlicense). #define C_EPSILON (1e-6) -#define C_PI (3.141592654f) // (3.14159265358979323846f) -#define TO_RAD (C_PI/180.f) -#define TO_DEG (180.f/C_PI) +#define C_PI (3.14159265358979323846f) // (3.141592654f) +#define TO_RAD (C_PI/180) +#define TO_DEG (180/C_PI) // ---------------------------------------------------------------------------- @@ -989,7 +988,6 @@ API void randset(uint64_t state); API uint64_t rand64(void); API double randf(void); // [0, 1) interval API int randi(int mini, int maxi); // [mini, maxi) interval -//API double rng(void); // [0..1) Lehmer RNG "minimal standard" // ---------------------------------------------------------------------------- @@ -1055,10 +1053,10 @@ enum EASE_FLAGS { EASE_OUT = 0, }; -API float ease(float t01, unsigned mode); // 0=linear,1=out_sine...31=inout_perlin - -API float ease_ping_pong(float t, float(*fn1)(float), float(*fn2)(float)); -API float ease_pong_ping(float t, float(*fn1)(float), float(*fn2)(float)); +API float ease(float t01, unsigned fn); // / 0-to-1 +API float ease_pong(float t01, unsigned fn); // \ 1-to-0 +API float ease_ping_pong(float t, unsigned fn1, unsigned fn2); // /\ 0-to-1-to-0 +API float ease_pong_ping(float t, unsigned fn1, unsigned fn2); // \/ 1-to-0-to-1 // ---------------------------------------------------------------------------- @@ -2047,6 +2045,64 @@ bool id_valid(uintptr_t id); #line 0 #line 1 "engine/split/v4k_pack.h" +// ----------------------------------------------------------------------------- +// semantic versioning in a single byte (octal) +// - rlyeh, public domain. +// +// - single octal byte that represents semantic versioning (major.minor.patch). +// - allowed range [0000..0377] ( <-> [0..255] decimal ) +// - comparison checks only major.minor tuple as per convention. + +API int semver( int major, int minor, int patch ); +API int semvercmp( int v1, int v2 ); + +#define SEMVER(major,minor,patch) (0100 * (major) + 010 * (minor) + (patch)) +#define SEMVERCMP(v1,v2) (((v1) & 0110) - ((v2) & 0110)) +#define SEMVERFMT "%03o" + +// ----------------------------------------------------------------------------- +// storage types. refer to vec2i/3i, vec2/3/4 if you plan to do math operations + +typedef struct byte2 { uint8_t x,y; } byte2; +typedef struct byte3 { uint8_t x,y,z; } byte3; +typedef struct byte4 { uint8_t x,y,z,w; } byte4; + +typedef struct int2 { int x,y; } int2; +typedef struct int3 { int x,y,z; } int3; +typedef struct int4 { int x,y,z,w; } int4; + +typedef struct uint2 { unsigned int x,y; } uint2; +typedef struct uint3 { unsigned int x,y,z; } uint3; +typedef struct uint4 { unsigned int x,y,z,w; } uint4; + +typedef struct float2 { float x,y; } float2; +typedef struct float3 { float x,y,z; } float3; +typedef struct float4 { float x,y,z,w; } float4; + +typedef struct double2 { double x,y; } double2; +typedef struct double3 { double x,y,z; } double3; +typedef struct double4 { double x,y,z,w; } double4; + +#define byte2(x,y) M_CAST(byte2, (uint8_t)(x), (uint8_t)(y) ) +#define byte3(x,y,z) M_CAST(byte3, (uint8_t)(x), (uint8_t)(y), (uint8_t)(z) ) +#define byte4(x,y,z,w) M_CAST(byte4, (uint8_t)(x), (uint8_t)(y), (uint8_t)(z), (uint8_t)(w) ) + +#define int2(x,y) M_CAST(int2, (int)(x), (int)(y) ) +#define int3(x,y,z) M_CAST(int3, (int)(x), (int)(y), (int)(z) ) +#define int4(x,y,z,w) M_CAST(int4, (int)(x), (int)(y), (int)(z), (int)(w) ) + +#define uint2(x,y) M_CAST(uint2, (unsigned)(x), (unsigned)(y) ) +#define uint3(x,y,z) M_CAST(uint3, (unsigned)(x), (unsigned)(y), (unsigned)(z) ) +#define uint4(x,y,z,w) M_CAST(uint4, (unsigned)(x), (unsigned)(y), (unsigned)(z), (unsigned)(w) ) + +#define float2(x,y) M_CAST(float2, (float)(x), (float)(y) ) +#define float3(x,y,z) M_CAST(float3, (float)(x), (float)(y), (float)(z) ) +#define float4(x,y,z,w) M_CAST(float4, (float)(x), (float)(y), (float)(z), (float)(w) ) + +#define double2(x,y) M_CAST(double2, (double)(x), (double)(y) ) +#define double3(x,y,z) M_CAST(double3, (double)(x), (double)(y), (double)(z) ) +#define double4(x,y,z,w) M_CAST(double4, (double)(x), (double)(y), (double)(z), (double)(w) ) + // ----------------------------------------------------------------------------- // compile-time fourcc, eightcc @@ -2075,16 +2131,25 @@ enum { #define cc7(a,b,c,d,e,f,g) cc8(,a,b,c,d,e,f,g) // ---------------------------------------------------------------------------- -// float conversion (text) +// text conversions +API char* ftoa1(float v); +API char* ftoa2(vec2 v); +API char* ftoa3(vec3 v); +API char* ftoa4(vec4 v); + +API float atof1(const char *s); API vec2 atof2(const char *s); API vec3 atof3(const char *s); API vec4 atof4(const char *s); -API char* ftoa(float f); -API char* ftoa2(vec2 v); -API char* ftoa3(vec3 v); -API char* ftoa4(vec4 v); +API char* itoa1(int v); +API char* itoa2(vec2i v); +API char* itoa3(vec3i v); + +API int atoi1(const char *s); +API vec2i atoi2(const char *s); +API vec3i atoi3(const char *s); // ---------------------------------------------------------------------------- // endianness @@ -2579,63 +2644,312 @@ API int64_t client_join(const char *ip, int port); #line 0 #line 1 "engine/split/v4k_obj.h" -// ----------------------------------------------------------------------------- -// semantic versioning in a single byte (octal) +// C objects framework // - rlyeh, public domain. // -// - single octal byte that represents semantic versioning (major.minor.patch). -// - allowed range [0000..0377] ( <-> [0..255] decimal ) -// - comparison checks only major.minor tuple as per convention. +// ## object limitations +// - 8-byte overhead per object +// - XX-byte overhead per object-entity +// - 32 components max per object-entity +// - 256 classes max per game +// - 256 references max per object +// - 1024K bytes max per object +// - 8 generations + 64K IDs per running instance (19-bit IDs) +// - support for pragma pack(1) structs not enabled by default. -API int semver( int major, int minor, int patch ); -API int semvercmp( int v1, int v2 ); +/* /!\ if you plan to use pragma pack(1) on any struct, you need #define OBJ_MIN_PRAGMAPACK_BITS 0 at the expense of max class size /!\ */ +#ifndef OBJ_MIN_PRAGMAPACK_BITS +//#define OBJ_MIN_PRAGMAPACK_BITS 3 // allows pragma packs >= 8. objsizew becomes 7<<3, so 1024 bytes max per class (default) +#define OBJ_MIN_PRAGMAPACK_BITS 1 // allows pragma packs >= 2. objsizew becomes 7<<1, so 256 bytes max per class +//#define OBJ_MIN_PRAGMAPACK_BITS 0 // allows pragma packs >= 1. objsizew becomes 7<<0, so 128 bytes max per class +#endif -#define SEMVER(major,minor,patch) (0100 * (major) + 010 * (minor) + (patch)) -#define SEMVERCMP(v1,v2) (((v1) & 0110) - ((v2) & 0110)) -#define SEMVERFMT "%03o" +#define OBJHEADER \ + union { \ + uintptr_t objheader; \ + struct { \ + uintptr_t objtype:8; \ + uintptr_t objheap:1; \ + uintptr_t objsizew:7; \ + uintptr_t objrefs:8; \ + uintptr_t objcomps:1; /* << can be removed? check payload ptr instead? */ \ + uintptr_t objnameid:16; \ + uintptr_t objid:ID_INDEX_BITS+ID_COUNT_BITS; /*16+3*/ \ + uintptr_t objunused:64-8-7-1-1-8-16-ID_INDEX_BITS-ID_COUNT_BITS; /*4*/ \ + }; \ + }; -// ----------------------------------------------------------------------------- -// storage types. refer to vec2i/3i, vec2/3/4 if you plan to do math operations +#define OBJ \ + struct { OBJHEADER }; -typedef struct byte2 { uint8_t x,y; } byte2; -typedef struct byte3 { uint8_t x,y,z; } byte3; -typedef struct byte4 { uint8_t x,y,z,w; } byte4; +// ---------------------------------------------------------------------------- +// syntax sugars -typedef struct int2 { int x,y; } int2; -typedef struct int3 { int x,y,z; } int3; -typedef struct int4 { int x,y,z,w; } int4; +#ifdef OBJTYPE +#undef OBJTYPE +#endif -typedef struct uint2 { unsigned int x,y; } uint2; -typedef struct uint3 { unsigned int x,y,z; } uint3; -typedef struct uint4 { unsigned int x,y,z,w; } uint4; +#define OBJTYPE(T) \ + OBJTYPE_##T -typedef struct float2 { float x,y; } float2; -typedef struct float3 { float x,y,z; } float3; -typedef struct float4 { float x,y,z,w; } float4; +#define OBJTYPEDEF(NAME,N) \ + enum { OBJTYPE(NAME) = N }; \ + STATIC_ASSERT( N <= 255 ); \ + STATIC_ASSERT( (sizeof(NAME) & ((1<objheader = HEAP ? id_make(PTR) : 0, /*should assign to .objid instead. however, id_make() returns shifted bits already*/ \ + (PTR)->objnameid = (OBJ_NAMEID), \ + (PTR)->objtype = (OBJ_TYPE), \ + (PTR)->objheap = (HEAP), \ + (PTR)->objsizew = (SIZEOF_OBJ>>OBJ_MIN_PRAGMAPACK_BITS)) +#define OBJ_CTOR_PTR(PTR,HEAP,OBJ_NAMEID,SIZEOF_OBJ,OBJ_TYPE) ( \ + OBJ_CTOR_HDR(PTR,HEAP,OBJ_NAMEID,SIZEOF_OBJ,OBJ_TYPE), \ + obj_ctor(PTR)) +#define OBJ_CTOR(TYPE, NAME, HEAP, PAYLOAD_SIZE, ...) (TYPE*)( \ + objtmp = (HEAP ? MALLOC(sizeof(TYPE)+(PAYLOAD_SIZE)) : ALLOCA(sizeof(TYPE)+(PAYLOAD_SIZE))), \ + *(TYPE*)objtmp = ((TYPE){ {0}, __VA_ARGS__}), \ + ((PAYLOAD_SIZE) ? memset((char*)objtmp + sizeof(TYPE), 0, (PAYLOAD_SIZE)) : objtmp), \ + ( OBJTYPES[ OBJTYPE(TYPE) ] = #TYPE ), \ + OBJ_CTOR_PTR(objtmp, HEAP,intern(NAME),sizeof(TYPE),OBJTYPE(TYPE)), \ + ifdef(debug, (obj_printf)(objtmp, va("%s", callstack(+16))), 0), \ + objtmp) + +#define obj(TYPE, ...) obj_ext(TYPE, #TYPE, __VA_ARGS__) +#define obj_ext(TYPE, NAME, ...) *OBJ_CTOR(TYPE, NAME, 0, sizeof(array(obj*)), __VA_ARGS__) + +#define obj_new(TYPE, ...) obj_new_ext(TYPE, #TYPE, __VA_ARGS__) +#define obj_new_ext(TYPE, NAME, ...) OBJ_CTOR(TYPE, NAME, 1, sizeof(array(obj*)), __VA_ARGS__) + +void* obj_malloc(unsigned sz); +void* obj_free(void *o); + +// ---------------------------------------------------------------------------- +// obj generics. can be extended. + +#define obj_ctor(o,...) obj_method(ctor, o, ##__VA_ARGS__) +#define obj_dtor(o,...) obj_method(dtor, o, ##__VA_ARGS__) + +#define obj_save(o,...) obj_method(save, o, ##__VA_ARGS__) +#define obj_load(o,...) obj_method(load, o, ##__VA_ARGS__) + +#define obj_test(o,...) obj_method(test, o, ##__VA_ARGS__) + +#define obj_init(o,...) obj_method(init, o, ##__VA_ARGS__) +#define obj_quit(o,...) obj_method(quit, o, ##__VA_ARGS__) +#define obj_tick(o,...) obj_method(tick, o, ##__VA_ARGS__) +#define obj_draw(o,...) obj_method(draw, o, ##__VA_ARGS__) + +#define obj_lerp(o,...) obj_method(lerp, o, ##__VA_ARGS__) +#define obj_edit(o,...) obj_method(edit, o, ##__VA_ARGS__) + +// --- syntax sugars + +#define obj_extend(T,func) (obj_##func[OBJTYPE(T)] = (void*)T##_##func) +#define obj_method(method,o,...) (obj_##method[((obj*)(o))->objtype](o,##__VA_ARGS__)) // (obj_##method[((obj*)(o))->objtype]((o), ##__VA_ARGS__)) + +#define obj_vtable(func,RC,...) RC macro(obj_##func)(){ __VA_ARGS__ }; RC (*obj_##func[256])() = { REPEAT256(macro(obj_##func)) }; +#define obj_vtable_null(func,RC) RC (*obj_##func[256])() = { 0 }; // null virtual table. will crash unless obj_extend'ed + +#define REPEAT16(f) f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f +#define REPEAT64(f) REPEAT16(f),REPEAT16(f),REPEAT16(f),REPEAT16(f) +#define REPEAT256(f) REPEAT64(f),REPEAT64(f),REPEAT64(f),REPEAT64(f) + +// --- declare vtables + +API extern void (*obj_ctor[256])(); ///- +API extern void (*obj_dtor[256])(); ///- + +API extern char* (*obj_save[256])(); ///- +API extern bool (*obj_load[256])(); ///- +API extern int (*obj_test[256])(); ///- + +API extern int (*obj_init[256])(); ///- +API extern int (*obj_quit[256])(); ///- +API extern int (*obj_tick[256])(); ///- +API extern int (*obj_draw[256])(); ///- + +API extern int (*obj_lerp[256])(); ///- +API extern int (*obj_edit[256])(); ///- + +// ---------------------------------------------------------------------------- +// core + +API uintptr_t obj_header(const void *o); + +API uintptr_t obj_id(const void *o); +API const char* obj_name(const void *o); + +API unsigned obj_typeid(const void *o); +API const char* obj_type(const void *o); + +API int obj_sizeof(const void *o); +API int obj_size(const void *o); // size of all members together in struct. may include padding bytes. + +API char* obj_data(void *o); // pointer to the first member in struct +API const char* obj_datac(const void *o); // const pointer to the first struct member + +API void* obj_payload(const void *o); // pointer right after last member in struct +API void* obj_zero(void *o); // reset all object members + +// ---------------------------------------------------------------------------- +// refcounting + +API void* obj_ref(void *oo); +API void* obj_unref(void *oo); + +// ---------------------------------------------------------------------------- +// scene tree + +// non-recursive +#define each_objchild(p,t,o) \ + (array(obj*)* children = obj_children(p); children; children = 0) \ + for(int _i = 1, _end = array_count(*children); _i < _end; ++_i) \ + for(t *o = (t *)((*children)[_i]); o && 0[*children]; o = 0) + +API obj* obj_detach(void *c); +API obj* obj_attach(void *o, void *c); + +API obj* obj_root(const void *o); +API obj* obj_parent(const void *o); +API array(obj*)*obj_children(const void *o); +API array(obj*)*obj_siblings(const void *o); + +API int obj_dumptree(const void *o); + +// ---------------------------------------------------------------------------- +// metadata + +API const char* obj_metaset(const void *o, const char *key, const char *value); +API const char* obj_metaget(const void *o, const char *key); + +// ---------------------------------------------------------------------------- +// stl + +API void* obj_swap(void *dst, void *src); +API void* obj_copy_fast(void *dst, const void *src); +API void* obj_copy(void *dst, const void *src); + +API int obj_comp_fast(const void *a, const void *b); +API int obj_comp(const void *a, const void *b); +API int obj_lesser(const void *a, const void *b); +API int obj_greater(const void *a, const void *b); +API int obj_equal(const void *a, const void *b); + +API uint64_t obj_hash(const void *o); + +// ---------------------------------------------------------------------------- +// debug + +API bool obj_hexdump(const void *oo); +API int obj_print(const void *o); + +API int obj_printf(const void *o, const char *text); +API int obj_console(const void *o); // obj_output() ? + +#define obj_printf(o, ...) obj_printf(o, va(__VA_ARGS__)) + +// ---------------------------------------------------------------------------- +// serialization + +API char* obj_saveini(const void *o); +API obj* obj_mergeini(void *o, const char *ini); +API obj* obj_loadini(void *o, const char *ini); + +API char* obj_savejson(const void *o); +API obj* obj_mergejson(void *o, const char *json); +API obj* obj_loadjson(void *o, const char *json); + +API char* obj_savebin(const void *o); +API obj* obj_mergebin(void *o, const char *sav); +API obj* obj_loadbin(void *o, const char *sav); + +API char* obj_savempack(const void *o); // @todo +API obj* obj_mergempack(void *o, const char *sav); // @todo +API obj* obj_loadmpack(void *o, const char *sav); // @todo + +API int obj_push(const void *o); +API int obj_pop(void *o); + +// ---------------------------------------------------------------------------- +// components + +API bool obj_addcomponent(void *object, unsigned c, void *ptr); +API bool obj_hascomponent(void *object, unsigned c); +API void* obj_getcomponent(void *object, unsigned c); +API bool obj_delcomponent(void *object, unsigned c); +API bool obj_usecomponent(void *object, unsigned c); +API bool obj_offcomponent(void *object, unsigned c); + +API char* entity_save(entity *self); + +// ---------------------------------------------------------------------------- +// reflection + +#define each_objmember(oo,TYPE,NAME,PTR) \ + (array(reflect_t) *found_ = members_find(quark(((obj*)oo)->objnameid)); found_; found_ = 0) \ + for(int it_ = 0, end_ = array_count(*found_); it_ != end_; ++it_ ) \ + for(reflect_t *R = &(*found_)[it_]; R; R = 0 ) \ + for(const char *NAME = R->name, *TYPE = R->type; NAME || TYPE; ) \ + for(void *PTR = ((char*)oo) + R->sz ; NAME || TYPE ; NAME = TYPE = 0 ) + +API void* obj_clone(const void *src); +API void* obj_merge(void *dst, const void *src); // @testme +API void* obj_mutate(void **dst, const void *src); +API void* obj_make(const char *str); + +// built-ins + +typedef enum OBJTYPE_BUILTINS { + OBJTYPE_obj = 0, + OBJTYPE_entity = 1, + OBJTYPE_vec2 = 2, + OBJTYPE_vec3 = 3, + OBJTYPE_vec4 = 4, + OBJTYPE_quat = 5, + OBJTYPE_mat33 = 6, + OBJTYPE_mat34 = 7, + OBJTYPE_mat44 = 8, + OBJTYPE_vec2i = 9, + OBJTYPE_vec3i = 10, +} OBJTYPE_BUILTINS; -#define double2(x,y) M_CAST(double2, (double)(x), (double)(y) ) -#define double3(x,y,z) M_CAST(double3, (double)(x), (double)(y), (double)(z) ) -#define double4(x,y,z,w) M_CAST(double4, (double)(x), (double)(y), (double)(z), (double)(w) ) #line 0 #line 1 "engine/split/v4k_profile.h" @@ -2716,14 +3030,14 @@ API void * function_find(const char *F); API reflect_t member_find(const char *T, const char *M); /// find specific member API void * member_findptr(void *obj, const char *T, const char *M); // @deprecate -API array(reflect_t) members_find(const char *T); +API array(reflect_t)* members_find(const char *T); // iterate members in a struct #define each_member(T,R) \ - (array(reflect_t)*found_ = map_find(members, intern(T)); found_; found_ = 0) \ + (array(reflect_t) *found_ = members_find(T); found_; found_ = 0) \ for(int it_ = 0, end_ = array_count(*found_); it_ != end_; ++it_ ) \ - for(reflect_t *R = (*found_)+it_; R; R = 0 ) + for(reflect_t *R = &(*found_)[it_]; R; R = 0 ) // private api, still exposed @@ -2756,7 +3070,7 @@ API unsigned rgba( uint8_t r, uint8_t g, uint8_t b, uint8_t a ); API unsigned bgra( uint8_t b, uint8_t g, uint8_t r, uint8_t a ); API unsigned rgbaf( float r, float g, float b, float a ); API unsigned bgraf( float b, float g, float r, float a ); -API float alpha( unsigned rgba ); +API unsigned alpha( unsigned rgba ); #define RGBX(rgb,x) ( ((rgb)&0xFFFFFF) | (((unsigned)(x))<<24) ) #define RGB3(r,g,b) ( (255<<24) | ((r)<<16) | ((g)<<8) | (b) ) @@ -3742,16 +4056,16 @@ API array(uint32_t) string32( const char *utf8 ); /// convert from utf8 to utf32 // ## string interning (quarks) // - rlyeh, public domain. -unsigned intern( const char *string ); -const char *quark( unsigned key ); +API unsigned intern( const char *string ); +API const char *quark( unsigned key ); typedef struct quarks_db { array(char) blob; array(vec2i) entries; } quarks_db; -unsigned quark_intern( quarks_db*, const char *string ); -const char *quark_string( quarks_db*, unsigned key ); +API unsigned quark_intern( quarks_db*, const char *string ); +API const char *quark_string( quarks_db*, unsigned key ); #line 0 #line 1 "engine/split/v4k_time.h" @@ -3781,7 +4095,7 @@ API void timer_destroy(unsigned timer_handle); // // also similar to a mongo object id, 12 bytes as follows: // - 4-byte timestamp (ss). epoch: Tuesday, 12 September 2023 6:06:56 -// - 2-byte (machine or app hash) +// - 2-byte (machine, hash or app id) // - 2-byte (thread-id) // - 4-byte (rand counter, that gets increased at every id creation) @@ -3853,7 +4167,7 @@ API void die(const char *message); API void alert(const char *message); API void hexdump( const void *ptr, unsigned len ); API void hexdumpf( FILE *fp, const void *ptr, unsigned len, int width ); -API void breakpoint(const char *optional_reason); +API void breakpoint(); API bool has_debugger(); API void trap_install(void);