diff --git a/core/build-config/tests.txt b/core/build-config/tests.txt index 75929d2..81290f8 100644 --- a/core/build-config/tests.txt +++ b/core/build-config/tests.txt @@ -1,3 +1,5 @@ tests/internal/basic-tag-registry-usage tests/internal/entity-lookup -tests/interface/entity-creation \ No newline at end of file +tests/interface/entity-queries +tests/interface/entity-creation +tests/interface/entity-tagging-api \ No newline at end of file diff --git a/core/exports/maintree/maintree.h b/core/exports/maintree/maintree.h index 1e1e6c6..6aaf7bb 100644 --- a/core/exports/maintree/maintree.h +++ b/core/exports/maintree/maintree.h @@ -14,6 +14,7 @@ typedef void MtState; typedef void (*MtEffectorFn) (MtEntity *entity, void *userdata, usz_t invocation_id); typedef void (*MtTaskMainFn) (MtTask *task, void *userdata); +typedef bool_t (*MtQueryMatcherFn) (MtEntity *entity, void *userdata); typedef struct { @@ -48,6 +49,9 @@ void mt_remove_effector(MtState *state, const char *effector_name); /// @param tag_names A nullpointer-terminated list of tag-ids that must be present in an entity for that entity to be effected. void mt_add_tagged_tick_effector(MtState *state, char *effector_name, usz_t ticks_delta, MtEffectorFn function, char **tag_names); +usz_t mt_query(MtState *state, const char **tags, MtEntity **entities, usz_t count); +usz_t mt_match_query(MtState *state, const char **tags, MtEntity **entities, usz_t count, MtQueryMatcherFn matcher, void *userdata); + void mt_tag_i64(MtEntity *entity, char *name, i64_t integer); void mt_tag_f64(MtEntity *entity, char *name, f64_t real); void mt_tag_str(MtEntity *entity, char *name, char *string); diff --git a/core/inc-c/entity.h b/core/inc-c/entity.h index 71717ad..c509d24 100644 --- a/core/inc-c/entity.h +++ b/core/inc-c/entity.h @@ -89,6 +89,7 @@ void mt_tag_vec2(MtEntity *entity, char *name, rr_vec2f_s vector); void mt_tag_vec3(MtEntity *entity, char *name, rr_vec3f_s vector); void mt_tag_vec4(MtEntity *entity, char *name, rr_vec4f_s vector); +bool_t mt_has_tag(MtEntity *entity, const char *name); void mt_untag(MtEntity *entity, char *name); i64_t mt_get_i64_tag(MtEntity *entity, char *name); diff --git a/core/inc-c/state.h b/core/inc-c/state.h index 1458d6b..703fc2b 100644 --- a/core/inc-c/state.h +++ b/core/inc-c/state.h @@ -9,6 +9,7 @@ #include typedef struct MtState MtState; +typedef bool_t (*MtQueryMatcherFn) (MtEntity *entity, void *userdata); struct MtState { @@ -25,4 +26,7 @@ void mt_start(MtState *state); MtEntity * mt_summon(MtState *state); void mt_drop(MtEntity *entity); +usz_t mt_query(MtState *state, const char **tags, MtEntity **entities, usz_t count); +usz_t mt_match_query(MtState *state, const char **tags, MtEntity **entities, usz_t count, MtQueryMatcherFn matcher, void *userdata); + #endif // MT_STATE_H diff --git a/core/inc-c/tag_registry.h b/core/inc-c/tag_registry.h index 835a7d9..95cad1d 100644 --- a/core/inc-c/tag_registry.h +++ b/core/inc-c/tag_registry.h @@ -30,6 +30,11 @@ struct MtTagEntry { u32_t identifier; char *label; + + usz_t entities_capacity; + usz_t num_entities; + /// @brief An array ofhe identifiers of all entities which have a tag of this type. + u32_t *entity_identifiers; }; struct MtTagCollection @@ -46,5 +51,9 @@ void mt_delete_tag_registry(MtTagRegistry registry); u32_t mt_get_tag_id(MtTagRegistry *registry, const char *tag_label); const char * mt_get_tag_label(MtTagRegistry *registry, u32_t tag_identifier); +MtTagEntry * mt_get_tag_entry(MtTagRegistry *registry, const char *tag_label); + +void mt_add_entity_to_tag_lookup_list(MtTagRegistry *registry, const char *tag_label, u32_t entity_identifier); +void mt_remove_entity_from_tag_lookup_list(MtTagRegistry *registry, const char *tag_label, u32_t entity_identifier); #endif // MT_TAG_REGISTRY_H diff --git a/core/src-c/entity_registry.c b/core/src-c/entity_registry.c index c64236b..99c3269 100644 --- a/core/src-c/entity_registry.c +++ b/core/src-c/entity_registry.c @@ -89,6 +89,7 @@ void mt_delete_entity_data(MtEntity *entity) { if(entity->tags != NULL) free(entity->tags); + entity->tags = NULL; } diff --git a/core/src-c/entity_tagging.c b/core/src-c/entity_tagging.c index 11fe236..ca5c0fd 100644 --- a/core/src-c/entity_tagging.c +++ b/core/src-c/entity_tagging.c @@ -20,6 +20,8 @@ i16_t mt_get_entity_tag_slot_index(MtEntity *entity, const char *name) MtTag * mt_get_entity_tag_slot(MtEntity *entity, char *name) { + MtState *state = entity->parent_state; + i16_t tag_slot_index = mt_get_entity_tag_slot_index(entity, name); if(tag_slot_index >= 0) return &entity->tags[tag_slot_index]; @@ -34,51 +36,87 @@ MtTag * mt_get_entity_tag_slot(MtEntity *entity, char *name) entity->tags = realloc(entity->tags, sizeof(struct MtTag) * entity->tags_capacity); } + entity->tags[entity->num_tags].identifier = mt_get_tag_id(&state->tag_registry, name); return &entity->tags[entity->num_tags++]; } void mt_tag_i64(MtEntity *entity, char *name, i64_t integer) { + MtState *state = entity->parent_state; MtTag *tag = mt_get_entity_tag_slot(entity, name); + + mt_add_entity_to_tag_lookup_list(&state->tag_registry, name, entity->identifier); tag->data.integer = integer; } void mt_tag_f64(MtEntity *entity, char *name, f64_t real) { + MtState *state = entity->parent_state; MtTag *tag = mt_get_entity_tag_slot(entity, name); + + mt_add_entity_to_tag_lookup_list(&state->tag_registry, name, entity->identifier); tag->data.real = real; } void mt_tag_str(MtEntity *entity, char *name, char *string) { + MtState *state = entity->parent_state; MtTag *tag = mt_get_entity_tag_slot(entity, name); + + mt_add_entity_to_tag_lookup_list(&state->tag_registry, name, entity->identifier); tag->data.string = string; } void mt_tag_ptr(MtEntity *entity, char *name, void *pointer) { + MtState *state = entity->parent_state; MtTag *tag = mt_get_entity_tag_slot(entity, name); + + mt_add_entity_to_tag_lookup_list(&state->tag_registry, name, entity->identifier); tag->data.pointer = pointer; } void mt_tag_vec2(MtEntity *entity, char *name, rr_vec2f_s vector) { + MtState *state = entity->parent_state; MtTag *tag = mt_get_entity_tag_slot(entity, name); + + mt_add_entity_to_tag_lookup_list(&state->tag_registry, name, entity->identifier); tag->data.vec2f = vector; } void mt_tag_vec3(MtEntity *entity, char *name, rr_vec3f_s vector) { + MtState *state = entity->parent_state; MtTag *tag = mt_get_entity_tag_slot(entity, name); + + mt_add_entity_to_tag_lookup_list(&state->tag_registry, name, entity->identifier); tag->data.vec3f = vector; } void mt_tag_vec4(MtEntity *entity, char *name, rr_vec4f_s vector) { + MtState *state = entity->parent_state; MtTag *tag = mt_get_entity_tag_slot(entity, name); + + mt_add_entity_to_tag_lookup_list(&state->tag_registry, name, entity->identifier); tag->data.vec4f = vector; } +bool_t mt_has_tag(MtEntity *entity, const char *name) +{ + MtState *state = entity->parent_state; + usz_t tag_id = mt_get_tag_id(&state->tag_registry, name); + usz_t tag_index = 0; + while(tag_index < entity->num_tags) + { + if(entity->tags[tag_index].identifier == tag_id) + return TRUE; + + ++tag_index; + } + return FALSE; +} void mt_untag(MtEntity *entity, char *name) { diff --git a/core/src-c/queries.c b/core/src-c/queries.c new file mode 100644 index 0000000..86791fd --- /dev/null +++ b/core/src-c/queries.c @@ -0,0 +1,107 @@ +#include +#include + +usz_t mt_find_tag_with_least_entries(MtState *state, const char **tags) +{ + u32_t least_entries_count = 0xffffffff; + usz_t least_entries_index = 0; + + usz_t tag_index = 0; + while(tags[tag_index] != NULL) + { + MtTagEntry *tag_entry = mt_get_tag_entry(&state->tag_registry, tags[tag_index]); + if(tag_entry->num_entities > least_entries_count) + { + least_entries_count = tag_entry->num_entities; + least_entries_index = tag_index; + } + ++tag_index; + } + return least_entries_index; +} + +usz_t mt_count_entities_with_matching_tags(MtState *state, const char **tags) +{ + usz_t num_matching_entities = 0; + + const char *least_used_tag = tags[mt_find_tag_with_least_entries(state, tags)]; + MtTagEntry *tag_entry = mt_get_tag_entry(&state->tag_registry, least_used_tag); + usz_t entity_index = 0; + while(entity_index < tag_entry->num_entities) + { + MtEntity *entity = mt_get_entity(state, tag_entry->entity_identifiers[entity_index]); + bool_t matching = TRUE; + usz_t wanted_tag_index = 0; + while(tags[wanted_tag_index] != NULL) + { + if(!mt_has_tag(entity, tags[wanted_tag_index])) + { + matching = FALSE; + break; + } + ++wanted_tag_index; + } + if(matching) + ++num_matching_entities; + + ++entity_index; + } + return num_matching_entities; +} + +usz_t mt_query(MtState *state, const char **tags, MtEntity **entities, usz_t count) +{ + if(count == 0) + return mt_count_entities_with_matching_tags(state, tags);; + + usz_t num_written_entities = 0; + const char *least_used_tag = tags[mt_find_tag_with_least_entries(state, tags)]; + MtTagEntry *tag_entry = mt_get_tag_entry(&state->tag_registry, least_used_tag); + usz_t entity_index = 0; + while((entity_index < tag_entry->num_entities) && (num_written_entities < count)) + { + MtEntity *entity = mt_get_entity(state, tag_entry->entity_identifiers[entity_index]); + bool_t matching = TRUE; + usz_t wanted_tag_index = 0; + while(tags[wanted_tag_index] != NULL) + { + if(!mt_has_tag(entity, tags[wanted_tag_index])) + { + matching = FALSE; + break; + } + ++wanted_tag_index; + } + if(matching) + { + entities[num_written_entities] = entity; + ++num_written_entities; + } + ++entity_index; + } + return num_written_entities; +} + +usz_t mt_match_query(MtState *state, const char **tags, MtEntity **entities, usz_t count, MtQueryMatcherFn matcher, void *userdata) +{ + usz_t num_matching_entities = mt_query(state, tags, entities, 0); + if(count == 0) + return num_matching_entities; + + // TODO: If 'num_matching_entities" is too big, this must be allocated on the heap! + MtEntity *generally_matching_entities[num_matching_entities]; + mt_query(state, tags, &generally_matching_entities[0], num_matching_entities); + + usz_t written_entities = 0; + usz_t entity_index = 0; + while((entity_index < num_matching_entities) && (written_entities < count)) + { + if(matcher(generally_matching_entities[entity_index], userdata)) + { + entities[written_entities] = generally_matching_entities[entity_index]; + ++written_entities; + } + ++entity_index; + } + return written_entities; +} diff --git a/core/src-c/tag_registry.c b/core/src-c/tag_registry.c index 91ee5bb..7cca3b5 100644 --- a/core/src-c/tag_registry.c +++ b/core/src-c/tag_registry.c @@ -3,6 +3,8 @@ #include #include +#define MT_INITIAL_ENTITIES_CAPACITY_PER_TAG 32 + MtTagRegistry mt_create_tag_registry(void *parent_state) { MtTagRegistry registry; @@ -93,7 +95,9 @@ MtTagEntry * mt_create_tag_hash_entry(MtTagRegistry *registry, const char *tag_l // TODO: There MUST be an arena allocator for this! entry->label = malloc(len_tag_label + 1); rr_memcopy(entry->label, tag_label, len_tag_label+1); - + entry->entities_capacity = MT_INITIAL_ENTITIES_CAPACITY_PER_TAG; + entry->entity_identifiers = calloc(sizeof(u32_t), entry->entities_capacity); + entry->num_entities = 0; entry->identifier = registry->next_identifier; ++registry->next_identifier; @@ -120,7 +124,7 @@ MtTagEntry * mt_create_tag_hash_entry(MtTagRegistry *registry, const char *tag_l return entry; } -u32_t mt_get_tag_id(MtTagRegistry *registry, const char *tag_label) +MtTagEntry * mt_get_tag_entry(MtTagRegistry *registry, const char *tag_label) { u32_t collection_index = mt_tag_label_to_hash(tag_label, registry->num_collections); MtTagCollection collection = registry->collections[collection_index]; @@ -129,12 +133,16 @@ u32_t mt_get_tag_id(MtTagRegistry *registry, const char *tag_label) { MtTagEntry *entry = ®istry->entries[collection.tag_indices[entry_index]]; if(rr_strings_equal(entry->label, tag_label)) - return entry->identifier; + return entry; ++entry_index; } - MtTagEntry *entry = mt_create_tag_hash_entry(registry, tag_label); - return entry->identifier; + return mt_create_tag_hash_entry(registry, tag_label); +} + +u32_t mt_get_tag_id(MtTagRegistry *registry, const char *tag_label) +{ + return mt_get_tag_entry(registry, tag_label)->identifier; } const char * mt_get_tag_label(MtTagRegistry *registry, u32_t tag_identifier) @@ -150,3 +158,47 @@ const char * mt_get_tag_label(MtTagRegistry *registry, u32_t tag_identifier) } return NULL; } + +u32_t mt_get_or_create_entity_id_slot_in_tag_entry(MtTagEntry *entry, u32_t entity_identifier) +{ + // Option 1: Find the entity identifier (if it was already registered) + + usz_t index = 0; + while(index < entry->num_entities) + { + if(entry->entity_identifiers[index] == entity_identifier) + return index; + + ++index; + } + + // Option 2: Get a slot for the entity identifier and write the ID. + + // Resize the entity_identifiers array if needed + + if(entry->num_entities >= entry->entities_capacity) + { + entry->entities_capacity *= 2; + if(entry->entities_capacity == 0) + entry->entities_capacity = MT_INITIAL_ENTITIES_CAPACITY_PER_TAG; + entry->entity_identifiers = realloc(entry->entity_identifiers, sizeof(u32_t) * entry->entities_capacity); + } + usz_t entity_identifier_index = entry->num_entities; + entry->entity_identifiers[entity_identifier_index] = entity_identifier; + ++entry->num_entities; + return entity_identifier_index; +} + +void mt_add_entity_to_tag_lookup_list(MtTagRegistry *registry, const char *tag_label, u32_t entity_identifier) +{ + MtTagEntry *entry = mt_get_tag_entry(registry, tag_label); + mt_get_or_create_entity_id_slot_in_tag_entry(entry, entity_identifier); +} + +void mt_remove_entity_from_tag_lookup_list(MtTagRegistry *registry, const char *tag_label, u32_t entity_identifier) +{ + MtTagEntry *entry = mt_get_tag_entry(registry, tag_label); + u32_t entity_index = mt_get_or_create_entity_id_slot_in_tag_entry(entry, entity_identifier); + entry->entity_identifiers[entity_index] = entry->entity_identifiers[entry->num_entities-1]; + --entry->num_entities; +} diff --git a/core/tests/interface/entity-queries/main.c b/core/tests/interface/entity-queries/main.c new file mode 100644 index 0000000..7626a0e --- /dev/null +++ b/core/tests/interface/entity-queries/main.c @@ -0,0 +1,49 @@ +#include +#include + +#include + +int main() +{ + MtState *state = mt_initialize("Interface Test"); + MtEntity *entity_1 = mt_summon(state); + mt_tag_i64(entity_1, "some_tag", 3); + mt_tag_i64(entity_1, "another_tag", 42); + mt_tag_i64(entity_1, "listed_tag", 1337); + + MtEntity *entity_2 = mt_summon(state); + mt_tag_i64(entity_2, "some_tag", 3); + mt_tag_i64(entity_2, "another_tag", 42); + + MtEntity *entity_3 = mt_summon(state); + mt_tag_i64(entity_3, "some_tag", 3); + mt_tag_i64(entity_3, "another_tag", 42); + mt_tag_i64(entity_3, "listed_tag", 236512); + + const char *tag_list[4] = + { + "some_tag", + "another_tag", + "listed_tag", + NULL + }; + usz_t num_matching_entities = mt_query(state, tag_list, NULL, 0); + printf("Number of matching entities: %ld\n", num_matching_entities); + + MtEntity *entities[8]; + mt_query(state, tag_list, entities, 8); + + printf("Entity 1: %p\nEntity 2: %p\nEntity 3: %p\n", (void *) entity_1, (void *) entity_2, (void *) entity_3); + usz_t entity_index = 0; + while(entity_index < num_matching_entities) + { + printf("Entity-Pointer: %p\n", (void *) entities[entity_index]); + ++entity_index; + } + + mt_drop(entity_1); + mt_drop(entity_2); + mt_drop(entity_3); + mt_cleanup(state); + return 0; +} diff --git a/core/tests/interface/entity-tagging-api/main.c b/core/tests/interface/entity-tagging-api/main.c new file mode 100644 index 0000000..0b7dc83 --- /dev/null +++ b/core/tests/interface/entity-tagging-api/main.c @@ -0,0 +1,26 @@ +#include +#include + +#define TEST_INTEGER 1337 + +int main() +{ + MtState *state = mt_initialize("Interface Test"); + MtEntity* entity = mt_summon(state); + mt_tag_i64(entity, "test-tag", TEST_INTEGER); + i64_t returned_integer = mt_get_i64_tag(entity, "test-tag"); + if(returned_integer != TEST_INTEGER) + { + puts("The integer returned from the get_i64_tag-function doesn't match the tag which was put in!"); + printf("Input: %d\nOutput: %ld\n", TEST_INTEGER, returned_integer); + mt_drop(entity); + mt_cleanup(state); + return -1; + } + + mt_drop(entity); + mt_cleanup(state); + puts("The program reached the end; this test is considered as passed. SUCCESS!"); + puts("Note: A memory leak check utility should be used to verify the success."); + return 0; +} diff --git a/core/tests/internal/entity-lookup/main.c b/core/tests/internal/entity-lookup/main.c index 1f3b406..56f86b6 100644 --- a/core/tests/internal/entity-lookup/main.c +++ b/core/tests/internal/entity-lookup/main.c @@ -5,7 +5,7 @@ int main() { - MtState *state = mt_initialize("Interface Test");; + MtState *state = mt_initialize("Internal Test"); MtEntity *entity = mt_summon(state); MtEntity *looked_up_entity = mt_get_entity(state, entity->identifier);