From 99bc0734d400f3f3c673ac8651eeec940d4252c8 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Tue, 10 Dec 2024 22:37:18 -0800 Subject: [PATCH] Add reusable script runtime to stage, reduce allocations during template instantiation --- distr/flecs.c | 106 ++++++++++++++++++++++++++++----- src/addons/script/script.c | 19 ++++++ src/addons/script/script.h | 3 + src/addons/script/template.c | 53 +++++++++++++---- src/addons/script/template.h | 6 ++ src/addons/script/visit_eval.c | 14 ++++- src/private_types.h | 5 ++ src/stage.c | 6 ++ test/script/project.json | 3 +- test/script/src/Template.c | 64 ++++++++++++++++++++ test/script/src/main.c | 7 ++- 11 files changed, 256 insertions(+), 30 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 30d88729c..666676b6e 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -881,6 +881,11 @@ struct ecs_stage_t { /* Caches for query creation */ ecs_vec_t variables; ecs_vec_t operations; + +#ifdef FLECS_SCRIPT + /* Thread specific runtime for script execution */ + ecs_script_runtime_t *runtime; +#endif }; /* Component monitor */ @@ -5454,12 +5459,18 @@ struct ecs_script_template_t { const ecs_type_info_t *type_info; }; +#define ECS_TEMPLATE_SMALL_SIZE (36) + /* Event used for deferring template instantiation */ typedef struct EcsTemplateSetEvent { ecs_entity_t template_entity; ecs_entity_t *entities; void *data; int32_t count; + + /* Storage for small template types */ + char data_storage[ECS_TEMPLATE_SMALL_SIZE]; + ecs_entity_t entity_storage; } EcsTemplateSetEvent; int flecs_script_eval_template( @@ -5521,6 +5532,9 @@ const char* flecs_term_parse( ecs_term_t *term, char *token_buffer); +ecs_script_runtime_t* flecs_script_runtime_get( + ecs_world_t *world); + void flecs_script_register_builtin_functions( ecs_world_t *world); @@ -17768,6 +17782,12 @@ void flecs_stage_free( flecs_commands_fini(stage, &stage->cmd_stack[i]); } +#ifdef FLECS_SCRIPT + if (stage->runtime) { + ecs_script_runtime_free(stage->runtime); + } +#endif + flecs_stack_fini(&stage->allocators.iter_stack); flecs_stack_fini(&stage->allocators.deser_stack); flecs_ballocator_fini(&stage->allocators.cmd_entry_chunk); @@ -57908,6 +57928,25 @@ void ecs_script_runtime_free( ecs_os_free(r); } +ecs_script_runtime_t* flecs_script_runtime_get( + ecs_world_t *world) +{ + ecs_stage_t *stage; + if (flecs_poly_is(world, ecs_stage_t)) { + stage = (ecs_stage_t*)world; + } else { + stage = world->stages[0]; + } + + ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!stage->runtime) { + stage->runtime = ecs_script_runtime_new(); + } + + return stage->runtime; +} + static int EcsScript_serialize( const ecs_serializer_t *ser, @@ -58453,22 +58492,37 @@ char* ecs_ptr_to_str( ECS_COMPONENT_DECLARE(EcsTemplateSetEvent); ECS_DECLARE(EcsTemplate); +static +void flecs_template_set_event_free(EcsTemplateSetEvent *ptr) { + if (ptr->entities != &ptr->entity_storage) { + ecs_os_free(ptr->entities); + } + if (ptr->data != ptr->data_storage) { + ecs_os_free(ptr->data); + } +} + static ECS_MOVE(EcsTemplateSetEvent, dst, src, { - ecs_os_free(dst->entities); - ecs_os_free(dst->data); - dst->entities = src->entities; - dst->data = src->data; - dst->template_entity = src->template_entity; - dst->count = src->count; + flecs_template_set_event_free(dst); + + *dst = *src; + + if (src->entities == &src->entity_storage) { + dst->entities = &dst->entity_storage; + } + + if (src->data == src->data_storage) { + dst->data = &dst->data_storage; + } + src->entities = NULL; src->data = NULL; }) static ECS_DTOR(EcsTemplateSetEvent, ptr, { - ecs_os_free(ptr->entities); - ecs_os_free(ptr->data); + flecs_template_set_event_free(ptr); }) /* Template ctor to initialize with default property values */ @@ -58528,8 +58582,18 @@ void flecs_script_template_defer_on_set( void *data) { EcsTemplateSetEvent evt; - evt.entities = ecs_os_memdup_n(it->entities, ecs_entity_t, it->count); - evt.data = ecs_os_memdup(data, ti->size * it->count); + + if ((it->count == 1) && ti->size <= ECS_TEMPLATE_SMALL_SIZE && !ti->hooks.dtor) { + /* This should be true for the vast majority of templates */ + evt.entities = &evt.entity_storage; + evt.data = evt.data_storage; + evt.entity_storage = it->entities[0]; + ecs_os_memcpy(evt.data, data, ti->size); + } else { + evt.entities = ecs_os_memdup_n(it->entities, ecs_entity_t, it->count); + evt.data = ecs_os_memdup(data, ti->size * it->count); + } + evt.count = it->count; evt.template_entity = template_entity; @@ -58569,7 +58633,11 @@ void flecs_script_template_instantiate( const EcsStruct *st = ecs_record_get(world, r, EcsStruct); ecs_script_eval_visitor_t v; - flecs_script_eval_visit_init(flecs_script_impl(script->script), &v, NULL); + ecs_script_eval_desc_t desc = { + .runtime = flecs_script_runtime_get(world) + }; + + flecs_script_eval_visit_init(flecs_script_impl(script->script), &v, &desc); ecs_vec_t prev_using = v.r->using; v.r->using = template->using_; @@ -58633,7 +58701,7 @@ void flecs_script_template_instantiate( } v.r->using = prev_using; - flecs_script_eval_visit_fini(&v, NULL); + flecs_script_eval_visit_fini(&v, &desc); } static @@ -61296,9 +61364,19 @@ int ecs_script_eval( ecs_script_impl_t *impl = flecs_script_impl( /* Safe, script will only be used for reading by visitor */ ECS_CONST_CAST(ecs_script_t*, script)); - flecs_script_eval_visit_init(impl, &v, desc); + + ecs_script_eval_desc_t priv_desc = {0}; + if (desc) { + priv_desc = *desc; + } + + if (!priv_desc.runtime) { + priv_desc.runtime = flecs_script_runtime_get(script->world); + } + + flecs_script_eval_visit_init(impl, &v, &priv_desc); int result = ecs_script_visit(impl, &v, flecs_script_eval_node); - flecs_script_eval_visit_fini(&v, desc); + flecs_script_eval_visit_fini(&v, &priv_desc); return result; } diff --git a/src/addons/script/script.c b/src/addons/script/script.c index 66faef64e..cb49e8529 100644 --- a/src/addons/script/script.c +++ b/src/addons/script/script.c @@ -275,6 +275,25 @@ void ecs_script_runtime_free( ecs_os_free(r); } +ecs_script_runtime_t* flecs_script_runtime_get( + ecs_world_t *world) +{ + ecs_stage_t *stage; + if (flecs_poly_is(world, ecs_stage_t)) { + stage = (ecs_stage_t*)world; + } else { + stage = world->stages[0]; + } + + ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!stage->runtime) { + stage->runtime = ecs_script_runtime_new(); + } + + return stage->runtime; +} + static int EcsScript_serialize( const ecs_serializer_t *ser, diff --git a/src/addons/script/script.h b/src/addons/script/script.h index b1d7d5bd1..6724fa2c7 100644 --- a/src/addons/script/script.h +++ b/src/addons/script/script.h @@ -101,6 +101,9 @@ const char* flecs_term_parse( ecs_term_t *term, char *token_buffer); +ecs_script_runtime_t* flecs_script_runtime_get( + ecs_world_t *world); + void flecs_script_register_builtin_functions( ecs_world_t *world); diff --git a/src/addons/script/template.c b/src/addons/script/template.c index 695dc22e7..c3d49de07 100644 --- a/src/addons/script/template.c +++ b/src/addons/script/template.c @@ -11,22 +11,37 @@ ECS_COMPONENT_DECLARE(EcsTemplateSetEvent); ECS_DECLARE(EcsTemplate); +static +void flecs_template_set_event_free(EcsTemplateSetEvent *ptr) { + if (ptr->entities != &ptr->entity_storage) { + ecs_os_free(ptr->entities); + } + if (ptr->data != ptr->data_storage) { + ecs_os_free(ptr->data); + } +} + static ECS_MOVE(EcsTemplateSetEvent, dst, src, { - ecs_os_free(dst->entities); - ecs_os_free(dst->data); - dst->entities = src->entities; - dst->data = src->data; - dst->template_entity = src->template_entity; - dst->count = src->count; + flecs_template_set_event_free(dst); + + *dst = *src; + + if (src->entities == &src->entity_storage) { + dst->entities = &dst->entity_storage; + } + + if (src->data == src->data_storage) { + dst->data = &dst->data_storage; + } + src->entities = NULL; src->data = NULL; }) static ECS_DTOR(EcsTemplateSetEvent, ptr, { - ecs_os_free(ptr->entities); - ecs_os_free(ptr->data); + flecs_template_set_event_free(ptr); }) /* Template ctor to initialize with default property values */ @@ -86,8 +101,18 @@ void flecs_script_template_defer_on_set( void *data) { EcsTemplateSetEvent evt; - evt.entities = ecs_os_memdup_n(it->entities, ecs_entity_t, it->count); - evt.data = ecs_os_memdup(data, ti->size * it->count); + + if ((it->count == 1) && ti->size <= ECS_TEMPLATE_SMALL_SIZE && !ti->hooks.dtor) { + /* This should be true for the vast majority of templates */ + evt.entities = &evt.entity_storage; + evt.data = evt.data_storage; + evt.entity_storage = it->entities[0]; + ecs_os_memcpy(evt.data, data, ti->size); + } else { + evt.entities = ecs_os_memdup_n(it->entities, ecs_entity_t, it->count); + evt.data = ecs_os_memdup(data, ti->size * it->count); + } + evt.count = it->count; evt.template_entity = template_entity; @@ -127,7 +152,11 @@ void flecs_script_template_instantiate( const EcsStruct *st = ecs_record_get(world, r, EcsStruct); ecs_script_eval_visitor_t v; - flecs_script_eval_visit_init(flecs_script_impl(script->script), &v, NULL); + ecs_script_eval_desc_t desc = { + .runtime = flecs_script_runtime_get(world) + }; + + flecs_script_eval_visit_init(flecs_script_impl(script->script), &v, &desc); ecs_vec_t prev_using = v.r->using; v.r->using = template->using_; @@ -191,7 +220,7 @@ void flecs_script_template_instantiate( } v.r->using = prev_using; - flecs_script_eval_visit_fini(&v, NULL); + flecs_script_eval_visit_fini(&v, &desc); } static diff --git a/src/addons/script/template.h b/src/addons/script/template.h index 9a5a42bb0..28e49f263 100644 --- a/src/addons/script/template.h +++ b/src/addons/script/template.h @@ -28,12 +28,18 @@ struct ecs_script_template_t { const ecs_type_info_t *type_info; }; +#define ECS_TEMPLATE_SMALL_SIZE (36) + /* Event used for deferring template instantiation */ typedef struct EcsTemplateSetEvent { ecs_entity_t template_entity; ecs_entity_t *entities; void *data; int32_t count; + + /* Storage for small template types */ + char data_storage[ECS_TEMPLATE_SMALL_SIZE]; + ecs_entity_t entity_storage; } EcsTemplateSetEvent; int flecs_script_eval_template( diff --git a/src/addons/script/visit_eval.c b/src/addons/script/visit_eval.c index c20d19268..9c63aeed2 100644 --- a/src/addons/script/visit_eval.c +++ b/src/addons/script/visit_eval.c @@ -1300,9 +1300,19 @@ int ecs_script_eval( ecs_script_impl_t *impl = flecs_script_impl( /* Safe, script will only be used for reading by visitor */ ECS_CONST_CAST(ecs_script_t*, script)); - flecs_script_eval_visit_init(impl, &v, desc); + + ecs_script_eval_desc_t priv_desc = {0}; + if (desc) { + priv_desc = *desc; + } + + if (!priv_desc.runtime) { + priv_desc.runtime = flecs_script_runtime_get(script->world); + } + + flecs_script_eval_visit_init(impl, &v, &priv_desc); int result = ecs_script_visit(impl, &v, flecs_script_eval_node); - flecs_script_eval_visit_fini(&v, desc); + flecs_script_eval_visit_fini(&v, &priv_desc); return result; } diff --git a/src/private_types.h b/src/private_types.h index aeaa39622..f2649dbce 100644 --- a/src/private_types.h +++ b/src/private_types.h @@ -230,6 +230,11 @@ struct ecs_stage_t { /* Caches for query creation */ ecs_vec_t variables; ecs_vec_t operations; + +#ifdef FLECS_SCRIPT + /* Thread specific runtime for script execution */ + ecs_script_runtime_t *runtime; +#endif }; /* Component monitor */ diff --git a/src/stage.c b/src/stage.c index 2a862d5ff..d4421c300 100644 --- a/src/stage.c +++ b/src/stage.c @@ -653,6 +653,12 @@ void flecs_stage_free( flecs_commands_fini(stage, &stage->cmd_stack[i]); } +#ifdef FLECS_SCRIPT + if (stage->runtime) { + ecs_script_runtime_free(stage->runtime); + } +#endif + flecs_stack_fini(&stage->allocators.iter_stack); flecs_stack_fini(&stage->allocators.deser_stack); flecs_ballocator_fini(&stage->allocators.cmd_entry_chunk); diff --git a/test/script/project.json b/test/script/project.json index 5b661e403..4e99947c9 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -327,7 +327,8 @@ "template_w_prefab_and_instance", "template_w_with_var", "template_w_with_prop", - "fold_const" + "fold_const", + "bulk_create_template" ] }, { "id": "Error", diff --git a/test/script/src/Template.c b/test/script/src/Template.c index a45a0f752..b390218dc 100644 --- a/test/script/src/Template.c +++ b/test/script/src/Template.c @@ -2376,3 +2376,67 @@ void Template_fold_const(void) { ecs_fini(world); } + +void Template_bulk_create_template(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_struct(world, { + .entity = ecs_id(Position), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + ecs_struct(world, { + .entity = ecs_id(Velocity), + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + const char *expr = + HEAD "template Position {" + LINE " prop x = f32: 0" + LINE " prop y = f32: 0" + LINE " Velocity: {$x + 5, $y + 5}" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr) == 0); + + Position p[] = { + {10, 20}, + {30, 40} + }; + + void *data[] = {p}; + + const ecs_entity_t *entities = ecs_bulk_init(world, &(ecs_bulk_desc_t) { + .count = 2, + .ids = {ecs_id(Position)}, + .data = data + }); + + test_assert(entities[0] != 0); + test_assert(entities[1] != 0); + + { + const Velocity *v = ecs_get(world, entities[0], Velocity); + test_assert(v != NULL); + test_int(v->x, 15); + test_int(v->y, 25); + } + + { + const Velocity *v = ecs_get(world, entities[1], Velocity); + test_assert(v != NULL); + test_int(v->x, 35); + test_int(v->y, 45); + } + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 1a561e205..092e82a32 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -322,6 +322,7 @@ void Template_template_w_prefab_and_instance(void); void Template_template_w_with_var(void); void Template_template_w_with_prop(void); void Template_fold_const(void); +void Template_bulk_create_template(void); // Testsuite 'Error' void Error_multi_line_comment_after_newline_before_newline_scope_open(void); @@ -2017,6 +2018,10 @@ bake_test_case Template_testcases[] = { { "fold_const", Template_fold_const + }, + { + "bulk_create_template", + Template_bulk_create_template } }; @@ -3791,7 +3796,7 @@ static bake_test_suite suites[] = { "Template", NULL, NULL, - 46, + 47, Template_testcases }, {