505 lines
14 KiB
C
505 lines
14 KiB
C
void *obj_initialize( void **ptr, char *type ) {
|
|
unsigned payload = ((unsigned char)type[0]) << 8; type[0] = '\1'; // @fixme: actually use this '\1' custom tag
|
|
#ifndef NDEBUG
|
|
strcatf(&type, "%s", callstack(+16)); // debug: for debugging purposes only
|
|
#endif
|
|
ptr[0] = OBJBOX((void *)type, payload);
|
|
|
|
return &ptr[1];
|
|
}
|
|
|
|
void obj_free( void *obj ) {
|
|
void *ptr = *((void**)obj - 1);
|
|
FREE( OBJUNBOX(ptr) );
|
|
obj = (void**)obj - 1;
|
|
FREE( obj );
|
|
}
|
|
|
|
void obj_del( void *obj ) {
|
|
unsigned type = obj_typeid(obj);
|
|
(type[dtor])(obj);
|
|
obj_free( obj );
|
|
}
|
|
|
|
unsigned obj_instances( const void *obj ) {
|
|
return OBJPAYLOAD3(obj) + 1;
|
|
}
|
|
|
|
// ---
|
|
|
|
const char *obj_output( const void *obj ) {
|
|
void* ptr = *((void**)obj - 1);
|
|
char *str = OBJUNBOX(ptr);
|
|
|
|
while( *str != '\n' ) ++str;
|
|
return (const char *)str + 1;
|
|
}
|
|
void (obj_printf)( void *obj, const char *text ) {
|
|
void* ptr = *((void**)obj - 1);
|
|
char* str = OBJUNBOX(ptr);
|
|
unsigned payload = OBJPAYLOAD16(ptr);
|
|
|
|
strcatf(&str, "%s", text);
|
|
|
|
*((void**)obj - 1) = OBJBOX(str, payload);
|
|
}
|
|
|
|
// ---
|
|
|
|
const char *obj_typeof( const void *obj ) {
|
|
void* ptr = *((void**)obj - 1);
|
|
ptr = OBJUNBOX(ptr);
|
|
|
|
char name[256]; sscanf((const char*)ptr + 1, "%[^\n]", name); // @fixme: overflow
|
|
return va("%s", name);
|
|
}
|
|
|
|
unsigned obj_typeid(const void *obj) {
|
|
void* ptr = *((void**)obj - 1);
|
|
unsigned payload = OBJPAYLOAD16(ptr);
|
|
return payload >> 8;
|
|
}
|
|
|
|
bool obj_typeeq( const void *obj1, const void *obj2 ) {
|
|
if( obj_typeid(obj1) != obj_typeid(obj2) ) return false;
|
|
return !strcmp( obj_typeof(obj1), obj_typeof(obj2) );
|
|
}
|
|
|
|
unsigned obj_typeid_from_name(const char *name) {
|
|
name += strbegi(name, "struct ") ? 8 : strbegi(name, "union ") ? 7 : 0;
|
|
unsigned typeid = hash_str(name) & 255; // @fixme: increase bits / decrease colliders (256 types only!)
|
|
ASSERT( typeid, "Name of given class has an empty (zeroed) hash. Limitation by design" );
|
|
|
|
static map(unsigned, char *) registered; // @fixme: add mutex
|
|
do_once map_init(registered, less_int, hash_int);
|
|
do_once map_insert(registered, 1, "(typeless)");
|
|
char **found = map_find(registered, typeid);
|
|
if(!found) map_insert(registered, typeid, STRDUP(name));
|
|
else ASSERT( !strcmp(name, *found), "Uh-oh, types collided. Please rename one of these two classes '%s'/'%s'", name, *found);
|
|
|
|
return typeid;
|
|
}
|
|
|
|
// ---
|
|
|
|
unsigned obj_sizeof(const void *obj) {
|
|
void *ptr = (void**)obj - 1;
|
|
return ALLOCSIZE(ptr) - sizeof(void*);
|
|
}
|
|
|
|
void obj_zero(void *obj) {
|
|
// clear console log
|
|
void* ptr = *((void**)obj - 1);
|
|
char* str = OBJUNBOX(ptr);
|
|
if( str[0] ) {
|
|
unsigned payload = OBJPAYLOAD16(ptr);
|
|
|
|
unsigned namelen = strlen(obj_typeof(obj));
|
|
str = REALLOC(str, 1+namelen+2); // preserve \1+name+\n+\0
|
|
str[1+namelen+2-1] = '\0';
|
|
|
|
*((void**)obj - 1) = OBJBOX(str, payload);
|
|
}
|
|
|
|
// reset data
|
|
dtor(obj);
|
|
memset(obj, 0, obj_sizeof(obj));
|
|
ctor(obj);
|
|
}
|
|
|
|
void obj_hexdumpf(FILE *out, const void *obj) {
|
|
hexdumpf(out, obj, obj_sizeof(obj), 16);
|
|
|
|
const char *output = obj_output(obj);
|
|
fprintf( out, "; ptr=[%p] sizeof=%d typeof=%s typeid=%#x refs=%d\n%s%s\n",
|
|
obj, obj_sizeof(obj), obj_typeof(obj), obj_typeid(obj), (int)(OBJPAYLOAD16(obj) & 0xFF),
|
|
output[0] ? output : "(no output)", output[0] ? "---" : "");
|
|
}
|
|
|
|
void obj_hexdump(const void *obj) {
|
|
obj_hexdumpf( stdout, obj );
|
|
}
|
|
|
|
// object: load/save
|
|
|
|
unsigned obj_load_buffer(void *obj, const void *src, unsigned srclen) {
|
|
unsigned objlen = obj_sizeof(obj);
|
|
if( srclen > objlen ) return 0; // @fixme: do something clever
|
|
memcpy(obj, src, srclen); // @fixme: do something clever
|
|
return objlen;
|
|
}
|
|
unsigned obj_load(void *obj, const array(char) buffer) {
|
|
unsigned bytes = buffer ? obj_load_buffer(obj, buffer, array_count((char*)buffer)) : 0;
|
|
return bytes;
|
|
}
|
|
unsigned obj_load_file(void *obj, FILE *fp) {
|
|
unsigned len = obj_sizeof(obj);
|
|
char *buffer = va("%*.s", len, "");
|
|
unsigned read = fread(buffer, 1, len, fp);
|
|
if( read != (1*len) ) {
|
|
return 0;
|
|
}
|
|
unsigned bytes = obj_load_buffer(obj, buffer, len);
|
|
return bytes;
|
|
}
|
|
|
|
unsigned obj_save_buffer(void *dst, unsigned cap, const void *obj) {
|
|
unsigned len = obj_sizeof(obj);
|
|
if( len > cap ) return 0;
|
|
memcpy(dst, obj, len); // @fixme: do something clever
|
|
return len;
|
|
}
|
|
array(char) obj_save(const void *obj) { // empty if error. must array_free() after use
|
|
array(char) data = 0;
|
|
unsigned len = obj_sizeof(obj);
|
|
array_resize(data, len);
|
|
unsigned bytes = obj_save_buffer(data, len, obj);
|
|
array_resize(data, bytes);
|
|
return data;
|
|
}
|
|
unsigned obj_save_file(FILE *fp, const void *obj) {
|
|
unsigned len = obj_sizeof(obj);
|
|
char *buffer = va("%*.s", len, "");
|
|
unsigned bytes = obj_save_buffer(buffer, len, obj);
|
|
if( bytes > 0 ) {
|
|
unsigned written = fwrite(buffer, 1, len, fp);
|
|
if( written == (1*len) ) {
|
|
return written;
|
|
}
|
|
}
|
|
return 0; // error
|
|
}
|
|
|
|
// ---
|
|
|
|
static int __thread global_ref_count; // @fixme: make it atomic
|
|
|
|
static void objref_check_atexit(void) {
|
|
if(global_ref_count > 0) fprintf(stderr, "Warn! Possible memory_leaks: %d refs not destroyed\n", global_ref_count);
|
|
if(global_ref_count < 0) fprintf(stderr, "Warn! Possible double free: %d refs double destroyed\n", -global_ref_count);
|
|
}
|
|
|
|
void* obj_ref(void *obj) {
|
|
do_once atexit(objref_check_atexit);
|
|
|
|
if( obj ) {
|
|
void *ptr = *((void**)obj - 1);
|
|
unsigned payload = OBJPAYLOAD16(ptr);
|
|
unsigned ref_count = payload & 255;
|
|
ASSERT(ref_count < 255, "Object cannot hold more than 256 refs. Limitation by design.");
|
|
|
|
*((void**)obj - 1) = OBJBOX(OBJUNBOX(ptr), payload + 1);
|
|
global_ref_count++;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
void* obj_unref(void *obj) {
|
|
if( obj ) {
|
|
void *ptr = *((void**)obj - 1);
|
|
unsigned payload = OBJPAYLOAD16(ptr);
|
|
unsigned ref_count = payload & 255;
|
|
|
|
*((void**)obj - 1) = OBJBOX(OBJUNBOX(ptr), payload - 1);
|
|
global_ref_count--;
|
|
|
|
if( ref_count <= 1 ) {
|
|
obj_del(obj);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
// ---
|
|
|
|
void dummy1() {}
|
|
#define dummy8 dummy1,dummy1,dummy1,dummy1,dummy1,dummy1,dummy1,dummy1
|
|
#define dummy64 dummy8,dummy8,dummy8,dummy8,dummy8,dummy8,dummy8,dummy8
|
|
#define dummy256 dummy64,dummy64,dummy64,dummy64
|
|
|
|
void (*dtor[256])() = { dummy256 };
|
|
void (*ctor[256])() = { dummy256 };
|
|
|
|
// ---
|
|
|
|
static set(uintptr_t) vtables; // @fixme: add mutex
|
|
|
|
void (obj_override)(const char *objclass, void (**vtable)(), void(*fn)()) {
|
|
do_once set_init(vtables, less_64, hash_64);
|
|
set_find_or_add(vtables, (uintptr_t)vtable);
|
|
|
|
vtable[ obj_typeid_from_name(objclass) ] = fn;
|
|
}
|
|
|
|
void (obj_extend)(const char *dstclass, const char *srcclass) { // wip, @testme
|
|
unsigned dst_id = obj_typeid_from_name(dstclass);
|
|
unsigned src_id = obj_typeid_from_name(srcclass);
|
|
|
|
// iterate src vtables, and assign them to dst
|
|
if(dst_id != src_id)
|
|
for each_set(vtables, void **, src_table) {
|
|
if( src_table[src_id] ) {
|
|
src_table[dst_id] = (void(*)()) (src_table[ src_id ]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---
|
|
|
|
void *obj_clone(const void *obj) { // @fixme: clone object console as well?
|
|
unsigned sz = obj_sizeof(obj);
|
|
const char *nm = obj_typeof(obj);
|
|
unsigned id = obj_typeid(obj);
|
|
|
|
void *obj2 = obj_initialize((void**)MALLOC(sizeof(void*)+sz), stringf("%c%s\n" "cloned" "\n", id, nm)); // STRDUP( OBJUNBOX(*((void**)obj - 1)) ));
|
|
memcpy(obj2, obj, sz);
|
|
return obj2;
|
|
}
|
|
|
|
void *obj_copy(void **dst, const void *src) {
|
|
if(!*dst) return *dst = obj_clone(src);
|
|
|
|
if( obj_typeeq(*dst, src) ) {
|
|
return memcpy(*dst, src, obj_sizeof(src));
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void *obj_mutate(void **dst_, const void *src) {
|
|
// mutate a class. ie, convert a given object class into a different one,
|
|
// while preserving the original metas and references as much as possible.
|
|
//
|
|
// @fixme: systems might be tracking objects in the future. the fact that we
|
|
// can reallocate a pointer (and hence, change its address) seems way too dangerous,
|
|
// as the tracking systems could crash when referencing a mutated object.
|
|
// solutions: do not reallocate if sizeof(new_class) > sizeof(old_class) maybe? good enough?
|
|
// also, optimization hint: no need to reallocate if both sizes matches, just copy contents.
|
|
|
|
if(!*dst_) return *dst_ = obj_clone(src);
|
|
|
|
void *dst = *dst_;
|
|
dtor(dst);
|
|
|
|
unsigned src_sz = obj_sizeof(src);
|
|
unsigned src_id = obj_typeid(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);
|
|
return dst;
|
|
}
|
|
|
|
|
|
#ifdef OBJ_DEMO
|
|
|
|
typedef struct MyObject {
|
|
char* id;
|
|
int x,y;
|
|
float rotation;
|
|
struct MyObject *next;
|
|
} MyObject;
|
|
|
|
void tests1() {
|
|
// Construct two objects
|
|
MyObject *root = obj_new(MyObject, 0);
|
|
MyObject *obj = obj_new(MyObject, "An identifier!", 0x11, 0x22, 3.1415f, root );
|
|
|
|
// Log some lines
|
|
obj_printf(root, "this is a logline #1\n");
|
|
obj_printf(root, "this is a logline #2\n");
|
|
obj_printf(root, "this is a logline #3\n");
|
|
|
|
obj_printf(obj, "yet another logline #1\n");
|
|
obj_printf(obj, "yet another logline #2\n");
|
|
|
|
// Dump contents of our objects
|
|
|
|
obj_hexdump(root);
|
|
obj_hexdump(obj);
|
|
|
|
// Save to mem
|
|
|
|
array(char) buffer = obj_save(obj);
|
|
printf("%d bytes\n", (int)array_count(buffer));
|
|
|
|
// Clear
|
|
|
|
obj_zero( obj );
|
|
obj_hexdump( obj );
|
|
|
|
// Reload
|
|
|
|
obj_load( obj, buffer );
|
|
obj_hexdump( obj );
|
|
|
|
// Copy tests
|
|
|
|
{
|
|
MyObject *clone = obj_clone(obj);
|
|
obj_hexdump(clone);
|
|
obj_del(clone);
|
|
}
|
|
|
|
{
|
|
MyObject *copy = 0;
|
|
obj_copy(©, obj);
|
|
obj_hexdump(copy);
|
|
obj_del(copy);
|
|
}
|
|
|
|
{
|
|
MyObject *copy = obj_new(MyObject, "A different identifier!", 0x33, 0x44, 0.0f, root );
|
|
obj_copy(©, obj);
|
|
obj_hexdump(copy);
|
|
obj_del(copy);
|
|
}
|
|
|
|
{
|
|
void *copy = obj_malloc(100, "an untyped class" );
|
|
obj_mutate(©, obj);
|
|
obj_hexdump(copy);
|
|
obj_copy(©, obj);
|
|
obj_hexdump(copy);
|
|
obj_del(copy);
|
|
}
|
|
|
|
// Benchmarking call overhead.
|
|
// We're here using dtor as a method to test. Since there is actually no
|
|
// destructor associated to this class, it will be safe to call it extensively (no double frees).
|
|
//
|
|
// results:
|
|
// 427 million calls/s @ old i5-4300/1.90Ghz laptop. compiled with "cl /Ox /Os /MT /DNDEBUG /GL /GF /arch:AVX2"
|
|
|
|
#ifndef N
|
|
#define N (INT32_MAX-1)
|
|
#endif
|
|
|
|
double t = (puts("benchmarking..."), -clock() / (double)CLOCKS_PER_SEC);
|
|
for( int i = 0; i < N; ++i ) {
|
|
dtor(root);
|
|
}
|
|
t += clock() / (double)CLOCKS_PER_SEC;
|
|
printf("Benchmark: %5.2f objcalls/s %5.2fM objcalls/s\n", N/(t), (N/1000)/(t*1000)); // ((N+N)*5) / (t) );
|
|
|
|
}
|
|
|
|
// --------------
|
|
|
|
#define dump(obj) dump[obj_typeid(obj)](obj)
|
|
#define area(obj) area[obj_typeid(obj)](obj)
|
|
|
|
extern void (*dump[256])();
|
|
extern float (*area[256])();
|
|
|
|
void (*dump[256])() = {0};
|
|
float (*area[256])() = {0};
|
|
|
|
// --------------
|
|
|
|
typedef struct box {
|
|
float w;
|
|
} box;
|
|
|
|
void box_ctor(box *b) { printf("box already constructed: box-w:%f\n", b->w); }
|
|
void box_dtor(box *b) { puts("deleting box..."); }
|
|
void box_dump(box *b) { printf("box-w:%f\n", b->w); }
|
|
float box_area(box *b) { return b->w * b->w; }
|
|
|
|
#define REGISTER_BOX \
|
|
obj_override(box, ctor); \
|
|
obj_override(box, dump); \
|
|
obj_override(box, area); \
|
|
obj_override(box, dtor);
|
|
|
|
typedef struct rect {
|
|
float w, h;
|
|
} rect;
|
|
|
|
void rect_dump(rect *r) { printf("rect-w:%f rect-h:%f\n", r->w, r->h); }
|
|
float rect_area(rect *r) { return r->w * r->h; }
|
|
void rect_dtor(rect *r) { puts("deleting rect..."); }
|
|
|
|
#define REGISTER_RECT \
|
|
obj_override(rect, dump); \
|
|
obj_override(rect, area); \
|
|
obj_override(rect, dtor);
|
|
|
|
void tests2() {
|
|
REGISTER_BOX
|
|
REGISTER_RECT
|
|
|
|
box *b = obj_new(box, 100);
|
|
rect *r = obj_new(rect, 100, 200);
|
|
|
|
dump(b);
|
|
dump(r);
|
|
|
|
printf("%f\n", area(b));
|
|
printf("%f\n", area(r));
|
|
|
|
obj_del(b);
|
|
obj_ref(r); obj_unref(r); //obj_del(r);
|
|
|
|
int *untyped = obj_malloc( sizeof(int) );
|
|
int *my_number = obj_malloc( sizeof(int), "a comment about my_number" );
|
|
char *my_text = obj_malloc( 32, "some debug info here" );
|
|
|
|
*untyped = 100;
|
|
*my_number = 123;
|
|
sprintf( my_text, "hello world" );
|
|
|
|
struct my_bitmap { int w, h, bpp; const char *pixels; };
|
|
struct my_bitmap *my_bitmap = obj_new(struct my_bitmap, 2,2,8, "\1\2\3\4");
|
|
|
|
printf( "%p(%s,%u)\n", my_bitmap, obj_typeof(my_bitmap), obj_typeid(my_bitmap) );
|
|
printf( "%d(%s,%d)\n", *untyped, obj_typeof(untyped), obj_typeid(untyped) );
|
|
printf( "%d(%s,%d)\n", *my_number, obj_typeof(my_number), obj_typeid(my_number) );
|
|
printf( "%s(%s,%d)\n", my_text, obj_typeof(my_text), obj_typeid(my_text) );
|
|
|
|
obj_printf(my_text, "hello world #1\n");
|
|
obj_printf(my_text, "hello world #2\n");
|
|
puts(obj_output(my_text));
|
|
|
|
printf( "%s(%s,%d)\n", my_text, obj_typeof(my_text), obj_typeid(my_text) );
|
|
|
|
printf( "equal?:%d\n", obj_typeeq(my_number, untyped) );
|
|
printf( "equal?:%d\n", obj_typeeq(my_number, my_number) );
|
|
printf( "equal?:%d\n", obj_typeeq(my_number, my_text) );
|
|
printf( "equal?:%d\n", obj_typeeq(my_number, my_bitmap) );
|
|
|
|
obj_free( untyped );
|
|
obj_free( my_text );
|
|
obj_free( my_bitmap );
|
|
obj_del( my_number ); // should not crash, even if allocated with obj_malloc()
|
|
}
|
|
|
|
int main() {
|
|
tests1();
|
|
tests2();
|
|
puts("ok");
|
|
}
|
|
|
|
/*
|
|
MEMBER( MyObject, char*, id );
|
|
MEMBER( MyObject, int, x );
|
|
MEMBER( MyObject, int, y );
|
|
MEMBER( MyObject, float, rotation, "(degrees)" );
|
|
MEMBER( MyObject, MyObject*, next, "(linked list)" );
|
|
*/
|
|
|
|
#define main main__
|
|
#endif
|