v4k-git-backup/engine/split/v4k_obj.c

1010 lines
33 KiB
C

// -----------------------------------------------------------------------------
// factory of handle ids, based on code by randy gaul (PD/Zlib licensed)
// - rlyeh, public domain
//
// [src] http://www.randygaul.net/wp-content/uploads/2021/04/handle_table.cpp
// [ref] http://bitsquid.blogspot.com.es/2011/09/managing-decoupling-part-4-id-lookup.html
// [ref] http://glampert.com/2016/05-04/dissecting-idhashindex/
// [ref] https://github.com/nlguillemot/dof/blob/master/viewer/packed_freelist.h
// [ref] https://gist.github.com/pervognsen/ffd89e45b5750e9ce4c6c8589fc7f253
// you cannot change this one: the number of ID_DATA_BITS you can store in a handle depends on ID_COUNT_BITS
#define ID_DATA_BITS (64-ID_COUNT_BITS)
typedef union id64 {
uint64_t h;
struct {
#if (ID_INDEX_BITS+ID_COUNT_BITS) != 64
uint64_t padding : 64-ID_INDEX_BITS-ID_COUNT_BITS;
#endif
uint64_t index : ID_INDEX_BITS;
uint64_t count : ID_COUNT_BITS;
};
} id64;
typedef struct id_factory id_factory;
id_factory id_factory_create(uint64_t capacity /*= 256*/);
id64 id_factory_insert(id_factory *f, uint64_t data);
uint64_t id_factory_getvalue(id_factory *f, id64 handle);
void id_factory_setvalue(id_factory *f, id64 handle, uint64_t data);
void id_factory_erase(id_factory *f, id64 handle);
bool id_factory_isvalid(id_factory *f, id64 handle);
void id_factory_destroy(id_factory *f);
// ---
typedef struct id_factory {
uint64_t freelist;
uint64_t capacity;
uint64_t canary;
union entry* entries;
} id_factory;
typedef union entry {
struct {
uint64_t data : ID_DATA_BITS;
uint64_t count : ID_COUNT_BITS;
};
uint64_t h;
} entry;
id_factory id_factory_create(uint64_t capacity) {
if(!capacity) capacity = 1ULL << ID_INDEX_BITS;
id_factory f = {0};
f.entries = CALLOC(1, sizeof(entry) * capacity);
f.capacity = capacity;
for (int i = 0; i < capacity - 1; ++i) {
f.entries[i].data = i + 1;
f.entries[i].count = 0;
}
f.entries[capacity - 1].h = 0;
f.entries[capacity - 1].data = ~0;
f.entries[capacity - 1].count = ~0;
f.canary = f.entries[capacity - 1].data;
return f;
}
void id_factory_destroy(id_factory *f) {
FREE(f->entries);
}
id64 id_factory_insert(id_factory *f, uint64_t data) {
// pop element off the free list
assert(f->freelist != f->canary && "max alive capacity reached");
uint64_t index = f->freelist;
f->freelist = f->entries[f->freelist].data;
// create new id64
f->entries[index].data = data;
id64 handle = {0};
handle.index = index;
handle.count = f->entries[index].count;
return handle;
}
void id_factory_erase(id_factory *f, id64 handle) {
// push id64 onto the freelist
uint64_t index = handle.index;
f->entries[index].data = f->freelist;
f->freelist = index;
// increment the count. this signifies a change in lifetime (this particular id64 is now dead)
// the next time this particular index is used in alloc, a new `count` will be used to uniquely identify
f->entries[index].count++;
}
uint64_t id_factory_getvalue(id_factory *f, id64 handle) {
uint64_t index = handle.index;
uint64_t count = handle.count;
assert(f->entries[index].count == count);
return f->entries[index].data;
}
void id_factory_setvalue(id_factory *f, id64 handle, uint64_t data) {
uint64_t index = handle.index;
uint64_t count = handle.count;
assert(f->entries[index].count == count);
f->entries[index].data = data;
}
bool id_factory_isvalid(id_factory *f, id64 handle) {
uint64_t index = handle.index;
uint64_t count = handle.count;
if (index >= f->capacity) return false;
return f->entries[index].count == count;
}
#if 0
// monitor history of a single entity by running `id_factory | find " 123."`
AUTORUN {
trap_install();
id_factory f = id_factory_create(optioni("--NUM",256));
array(id64) ids = 0;
for( int i = 0 ; ; ++i, i &= ((1ULL << ID_INDEX_BITS) - 1) ) { // infinite wrap
printf("count_ids(%d) ", array_count(ids));
bool insert = randf() < 0.49; // biased against deletion
if( insert ) {
id64 h = id_factory_insert(&f, i);
array_push(ids, h);
printf("add %llu.%llu\n", h.index, h.count);
} else {
int count = array_count(ids);
if( count ) {
int chosen = randi(0,count);
printf("del %d.\n", chosen);
id64 h = ids[chosen];
id_factory_erase(&f, h);
array_erase(ids, chosen);
}
}
}
}
#endif
// ----------------------------------------------------------------------
// public api
static id_factory fid; // @fixme: threadsafe
uintptr_t id_make(void *ptr) {
do_once fid = id_factory_create(0), id_factory_insert(&fid, 0); // init and reserve id(0)
id64 newid = id_factory_insert(&fid, (uint64_t)(uintptr_t)ptr ); // 48-bit effective addr
return newid.h;
}
void *id_handle(uintptr_t id) {
return (void *)(uintptr_t)id_factory_getvalue(&fid, ((id64){ (uint64_t)id }) );
}
void id_dispose(uintptr_t id) {
id_factory_erase(&fid, ((id64){ (uint64_t)id }) );
}
bool id_valid(uintptr_t id) {
return id_factory_isvalid(&fid, ((id64){ (uint64_t)id }) );
}
// ----------------------------------------------------------------------
// C objects framework
// - rlyeh, public domain.
// --- 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"
obj_vtable_null(menu, int );
obj_vtable_null(aabb, int );
obj_vtable_null(icon, char* );
// ----------------------------------------------------------------------------
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,sz,OBJTYPE_obj);
return ptr;
}
void *obj_free(void *o) {
if( !((obj*)o)->objrefs ) {
obj_detach(o);
obj_dtor(o);
//obj_zero(o);
if( ((obj*)o)->objheap ) {
FREE(o);
}
return 0;
}
return o; // cannot destroy: object is still referenced
}
// ----------------------------------------------------------------------------
// core
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<<OBJ_MIN_PRAGMAPACK_BITS);
}
void *obj_zero(void *o) { // reset all object members
return memset(obj_data(o), 0, obj_size(o)), o;
}
static
void test_obj_core() {
obj *r = obj_new_ext(obj, "root");
obj *s = obj_new_ext(obj, "root");
test(r);
test( 0 == strcmp(obj_type(r), "obj") );
test( 0 == strcmp(obj_name(r), "root") );
test( OBJTYPE_obj == obj_typeid(r) );
test(s);
test( 0 == strcmp(obj_type(s), "obj") );
test( 0 == strcmp(obj_name(s), "root") );
test( OBJTYPE_obj == obj_typeid(s) );
test( obj_id(r) != 0 );
test( obj_id(s) != 0 );
test( obj_id(r) != obj_id(s) );
obj t = obj(obj); obj_setname(&t, "root");
obj u = obj(obj); obj_setname(&u, "root");
test(&t);
test( 0 == strcmp(obj_type(&t), "obj") );
test( 0 == strcmp(obj_name(&t), "root") );
test( OBJTYPE_obj == obj_typeid(&t) );
test(&u);
test( 0 == strcmp(obj_type(&u), "obj") );
test( 0 == strcmp(obj_name(&u), "root") );
test( OBJTYPE_obj == obj_typeid(&u) );
test( obj_id(&t) == 0 );
test( obj_id(&u) == 0 );
test( obj_id(&t) == obj_id(&u) );
}
// ----------------------------------------------------------------------------
// refcounting
// static int __thread global_ref_count; // @fixme: make it atomic
// static void objref_check_atexit(void) {
// if(global_ref_count) tty_color(YELLOW), fprintf(stderr, "Warn! obj_refs not zero (%d)\n", global_ref_count), tty_color(0);
// }
// AUTORUN { (atexit)(objref_check_atexit); }
void *obj_ref(void *oo) {
obj* o = (obj*)oo;
int num = o->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*)o)->objchildren;
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 0[*c]; // (*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);
array_erase_slow(*oo, i);
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;
void *obj_setmeta(void *o, const char *key, const char *value) {
void *ret = 0;
do_threadlock(oms_lock) {
if(!oms) map_init_int(oms);
int *q = map_find_or_add(oms, intern(va("%p-%s",(void*)obj_id((obj*)o),key)), 0);
if(!*q && !value[0]) {} else *q = intern(value);
quark(*q), ret = o;
}
return ret;
}
const char* obj_meta(const void *o, const char *key) {
const char *ret = 0;
do_threadlock(oms_lock) {
if(!oms) map_init_int(oms);
int *q = map_find_or_add(oms, intern(va("%p-%s",(void*)obj_id((obj*)o),key)), 0);
ret = quark(*q);
}
return ret;
}
void *obj_setname(void *o, const char *name) {
ifdef(debug,((obj*)o)->objname = name);
return obj_setmeta(o, "name", name);
}
const char *obj_name(const void *o) {
const char *objname = obj_meta(o, "name");
return objname[0] ? objname : "obj";
}
static
void test_obj_metadatas( void *o1 ) {
obj *o = (obj *)o1;
test( !strcmp("", obj_meta(o, "has_passed_test")) );
test( obj_setmeta(o, "has_passed_test", "yes") );
test( !strcmp("yes", obj_meta(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] %p\n",
obj_name(o), obj_type(o),
(int)o->objid>>16, (int)o->objid&0xffff, (int)o->objunused,
obj_sizeof(o), (void*)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("%p", (void*)*(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, "rgb") ) return rgbatoa(*(unsigned*)p);
else if( !strcmp(type, "rgba") ) return rgbatoa(*(unsigned*)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, "rgb") ) return !!memcpy(P, (u = atorgba(str), &u), sizeof(u));
else if( !strcmp(type, "rgba") ) return !!memcpy(P, (u = atorgba(str), &u), sizeof(u));
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, "%63[^]]", 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 map(void*,array(char*)) obj_stack;
int obj_push(const void *o) {
if(!obj_stack) map_init_ptr(obj_stack);
array(char*) *found = map_find_or_add(obj_stack,(void*)o,0);
char *bin = STRDUP(obj_saveini(o)); // @todo: savebin
array_push(*found, bin);
return 1;
}
int obj_pop(void *o) {
if(!obj_stack) map_init_ptr(obj_stack);
array(char*) *found = map_find_or_add(obj_stack,(void*)o,0);
char **bin = array_back(*found);
if( bin ) {
int rc = !!obj_loadini(o, *bin); // @todo: loadbin
if( array_count(*found) > 1 ) {
FREE(*bin);
array_pop(*found);
}
return rc;
}
return 0;
}
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(entity *e, unsigned c, void *ptr) {
e->cflags |= (3ULL << c);
e->c[c & (OBJCOMPONENTS_MAX-1)] = ptr;
return 1;
}
bool obj_hascomponent(entity *e, unsigned c) {
return !!(e->cflags & (3ULL << c));
}
void* obj_getcomponent(entity *e, unsigned c) {
return e->c[c & (OBJCOMPONENTS_MAX-1)];
}
bool obj_delcomponent(entity *e, unsigned c) {
e->cflags &= ~(3ULL << c);
e->c[c & (OBJCOMPONENTS_MAX-1)] = NULL;
return 1;
}
bool obj_usecomponent(entity *e, unsigned c) {
e->cflags |= (1ULL << c);
return 1;
}
bool obj_offcomponent(entity *e, unsigned c) {
e->cflags &= ~(1ULL << c);
return 0;
}
char *entity_save(entity *self) {
char *sav = obj_saveini(self);
return sav;
}
static
void entity_register() {
do_once {
STRUCT(entity, uintptr_t, cflags);
obj_extend(entity, save);
}
}
AUTORUN{
entity_register();
}
static
void test_obj_ecs() {
entity_register(); // why is this required here? autorun init fiasco?
entity *e = entity_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_mutate(void *dst, const void *src) {
((obj*)dst)->objheader = ((const obj *)src)->objheader;
#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
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_clone(const void *src) {
int sz = sizeof(obj) + obj_size(src) + sizeof(array(obj*));
enum { N = 8 }; sz = ((sz + (N - 1)) & -N); // Round up to N-byte boundary
obj *ptr = obj_malloc( sz );
obj_mutate(ptr, src); // 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_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 ? "%63[^]]" : "%63[^:=]", 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 obj_new(obj);
obj *ptr = CALLOC(1, found->sz + (has_components+1) * 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);
obj_setname(ptr, name); // found->id);
return ptr; // returns partial construction as well. @todo: just return `ret` for a more strict built/failed policy
}