From 9560ed55b9d1bc85d250180ea66f857c3c1d0350 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sun, 17 Jan 2021 21:45:35 -0800 Subject: [PATCH] #124 add dynamic buffer examples --- README.md | 4 +- .../include/dynamic_buffer.h | 16 +++ .../include/dynamic_buffer/bake_config.h | 24 ++++ examples/c/53_dynamic_buffer/project.json | 12 ++ examples/c/53_dynamic_buffer/src/main.c | 131 ++++++++++++++++++ .../include/dynamic_buffer.h | 16 +++ .../include/dynamic_buffer/bake_config.h | 24 ++++ examples/cpp/53_dynamic_buffer/project.json | 13 ++ examples/cpp/53_dynamic_buffer/src/main.cpp | 114 +++++++++++++++ 9 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 examples/c/53_dynamic_buffer/include/dynamic_buffer.h create mode 100644 examples/c/53_dynamic_buffer/include/dynamic_buffer/bake_config.h create mode 100644 examples/c/53_dynamic_buffer/project.json create mode 100644 examples/c/53_dynamic_buffer/src/main.c create mode 100644 examples/cpp/53_dynamic_buffer/include/dynamic_buffer.h create mode 100644 examples/cpp/53_dynamic_buffer/include/dynamic_buffer/bake_config.h create mode 100644 examples/cpp/53_dynamic_buffer/project.json create mode 100644 examples/cpp/53_dynamic_buffer/src/main.cpp diff --git a/README.md b/README.md index 5c3eae071..b9a0262f1 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,9 @@ The framework code and example code is compiled warning free on all platforms wi Performance is tracked on a per-release basis, with the results for the latest release published here: https://github.com/SanderMertens/ecs_benchmark ### API stability -API stability is guaranteed between minor releases, except in the rare case when an API is found to be an obvious source of confusion or bugs. ABI stability is not guaranteed inbetween versions. Types and function signatures may change as long as they do not require changes in the application code, which is why applications should rebuild after upgrading to a new revision. Headers under include/private are not part of the public API, and may introduce breaking changes at any point. +API stability is guaranteed between minor releases, except in the rare case when an API is found to be an obvious source of confusion or bugs. ABI stability is not guaranteed inbetween versions. When breaking changes do happen, the release notes will mention it with potential workarounds. + +Types and function signatures may change as long as they do not require changes in the application code, which is why applications should rebuild after upgrading to a new revision. Headers under include/private are not part of the public API, and may introduce breaking changes at any point. It is generally safe to use the master branch, which contains the latest version of the code. New features that are on master but are not yet part of a release may still see changes in their API. Once a feature is part of a release, its API will not change until at least the next major release. diff --git a/examples/c/53_dynamic_buffer/include/dynamic_buffer.h b/examples/c/53_dynamic_buffer/include/dynamic_buffer.h new file mode 100644 index 000000000..2db0b4fdf --- /dev/null +++ b/examples/c/53_dynamic_buffer/include/dynamic_buffer.h @@ -0,0 +1,16 @@ +#ifndef DYNAMIC_BUFFER_H +#define DYNAMIC_BUFFER_H + +/* This generated file contains includes for project dependencies */ +#include "dynamic_buffer/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/examples/c/53_dynamic_buffer/include/dynamic_buffer/bake_config.h b/examples/c/53_dynamic_buffer/include/dynamic_buffer/bake_config.h new file mode 100644 index 000000000..2316e49c5 --- /dev/null +++ b/examples/c/53_dynamic_buffer/include/dynamic_buffer/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * This file is generated by bake.lang.c for your convenience. Headers of + * dependencies will automatically show up in this file. Include bake_config.h + * in your main project file. Do not edit! */ + +#ifndef DYNAMIC_BUFFER_BAKE_CONFIG_H +#define DYNAMIC_BUFFER_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include + +#endif + diff --git a/examples/c/53_dynamic_buffer/project.json b/examples/c/53_dynamic_buffer/project.json new file mode 100644 index 000000000..1926f04f4 --- /dev/null +++ b/examples/c/53_dynamic_buffer/project.json @@ -0,0 +1,12 @@ +{ + "id": "dynamic_buffer", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/examples/c/53_dynamic_buffer/src/main.c b/examples/c/53_dynamic_buffer/src/main.c new file mode 100644 index 000000000..d4c4883c6 --- /dev/null +++ b/examples/c/53_dynamic_buffer/src/main.c @@ -0,0 +1,131 @@ +#include + +typedef struct { + float x, y; +} Position; + +/* Non-POD component type with a dynamic buffer */ +typedef struct { + int *data; + size_t count; +} DynamicBuffer; + +/* Lifecycle callbacks for the DynamicBuffer. These ensure that when a component + * is created, destructed, copied or moved no memory corruption or leakage + * happens. */ +ECS_CTOR(DynamicBuffer, ptr, { + printf("DynamicBuffer::ctor\n"); + ptr->data = NULL; + ptr->count = 0; +}) + +ECS_DTOR(DynamicBuffer, ptr, { + printf("DynamicBuffer::dtor\n"); + free(ptr->data); +}) + +ECS_COPY(DynamicBuffer, dst, src, { + printf("DynamicBuffer::copy\n"); + if (dst->data) { + free(dst->data); + } + + size_t size = sizeof(int) * src->count; + dst->data = malloc(size); + dst->count = src->count; + memcpy(dst->data, src->data, size); +}) + +ECS_MOVE(DynamicBuffer, dst, src, { + printf("DynamicBuffer::move\n"); + if (dst->data) { + free(dst->data); + } + + dst->data = src->data; + dst->count = src->count; + + src->data = NULL; + src->count = 0; +}) + +/* Forward declare component handles */ +ECS_COMPONENT_DECLARE(Position); +ECS_COMPONENT_DECLARE(DynamicBuffer); + +/* Add an element to a new or existing buffer */ +void add_elem(ecs_world_t *ecs, ecs_entity_t e, int value) { + DynamicBuffer *ptr = ecs_get_mut(ecs, e, DynamicBuffer, NULL); + + ptr->count ++; + ptr->data = realloc(ptr->data, ptr->count * sizeof(int)); + ptr->data[ptr->count - 1] = value; +} + +/* Remove element from buffer */ +void remove_elem(ecs_world_t *ecs, ecs_entity_t e, size_t elem) { + DynamicBuffer *ptr = ecs_get_mut(ecs, e, DynamicBuffer, NULL); + + size_t last = ptr->count - 1; + + if (last >= elem) { + if (last - elem) { + ptr->data[elem] = ptr->data[last]; + } + + ptr->count --; + } +} + +/* Get element from a buffer */ +int* get_elem(ecs_world_t *ecs, ecs_entity_t e, size_t elem) { + const DynamicBuffer *ptr = ecs_get(ecs, e, DynamicBuffer); + + if (ptr && (ptr->count > elem)) { + return &ptr->data[elem]; + } else { + return NULL; + } +} + +int main(int argc, char *argv[]) { + ecs_world_t *ecs = ecs_init_w_args(argc, argv); + + /* Register ids for forward declared components */ + ECS_COMPONENT_DEFINE(ecs, Position); + ECS_COMPONENT_DEFINE(ecs, DynamicBuffer); + + /* Register lifecycle functions for comopnent */ + ecs_set_component_actions(ecs, DynamicBuffer, { + ecs_ctor(DynamicBuffer), + ecs_dtor(DynamicBuffer), + ecs_copy(DynamicBuffer), + ecs_move(DynamicBuffer), + NULL /* optional context */ + }); + + ecs_entity_t e = ecs_new_id(ecs); + + /* Add 3 elements to the buffer. The first add will add the DynamicBuffer + * element to the entity. */ + add_elem(ecs, e, 10); + add_elem(ecs, e, 20); + add_elem(ecs, e, 30); + + printf("Elem 1 = %d\n", *get_elem(ecs, e, 1)); + + /* Remove element. This will move the last element from the buffer to the + * removed element. */ + remove_elem(ecs, e, 1); + + printf("Elem 1 = %d (after remove)\n", *get_elem(ecs, e, 1)); + + /* Add component. This causes the entity to move between tables, and will + * invoke DynamicComponent::move to copy the component value from the src to + * the dst table. This also invokes DynamicComponent::ctor to construct the + * component in the dst table. */ + ecs_add(ecs, e, Position); + + /* This will invoke DynamicComponent::dtor. */ + return ecs_fini(ecs); +} diff --git a/examples/cpp/53_dynamic_buffer/include/dynamic_buffer.h b/examples/cpp/53_dynamic_buffer/include/dynamic_buffer.h new file mode 100644 index 000000000..2db0b4fdf --- /dev/null +++ b/examples/cpp/53_dynamic_buffer/include/dynamic_buffer.h @@ -0,0 +1,16 @@ +#ifndef DYNAMIC_BUFFER_H +#define DYNAMIC_BUFFER_H + +/* This generated file contains includes for project dependencies */ +#include "dynamic_buffer/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/examples/cpp/53_dynamic_buffer/include/dynamic_buffer/bake_config.h b/examples/cpp/53_dynamic_buffer/include/dynamic_buffer/bake_config.h new file mode 100644 index 000000000..2316e49c5 --- /dev/null +++ b/examples/cpp/53_dynamic_buffer/include/dynamic_buffer/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * This file is generated by bake.lang.c for your convenience. Headers of + * dependencies will automatically show up in this file. Include bake_config.h + * in your main project file. Do not edit! */ + +#ifndef DYNAMIC_BUFFER_BAKE_CONFIG_H +#define DYNAMIC_BUFFER_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include + +#endif + diff --git a/examples/cpp/53_dynamic_buffer/project.json b/examples/cpp/53_dynamic_buffer/project.json new file mode 100644 index 000000000..4483612e9 --- /dev/null +++ b/examples/cpp/53_dynamic_buffer/project.json @@ -0,0 +1,13 @@ +{ + "id": "dynamic_buffer", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/examples/cpp/53_dynamic_buffer/src/main.cpp b/examples/cpp/53_dynamic_buffer/src/main.cpp new file mode 100644 index 000000000..f41cd2d8d --- /dev/null +++ b/examples/cpp/53_dynamic_buffer/src/main.cpp @@ -0,0 +1,114 @@ +#include +#include + +struct Position { + float x, y; +}; + +/* Non-POD type. Don't use std::vector on purpose to demonstrate how a type + * can register its own lifecycle actions. */ +struct DynamicBuffer { + DynamicBuffer() : data(nullptr), count(0) { + std::cout << "DynamicBuffer::ctor" << std::endl; + } + + ~DynamicBuffer() { + std::cout << "DynamicBuffer::dtor" << std::endl; + free(data); + } + + DynamicBuffer& operator=(const DynamicBuffer& src) { + std::cout << "DynamicBuffer::copy" << std::endl; + if (data) { + free(data); + } + + size_t size = sizeof(int) * src.count; + data = static_cast(malloc(size)); + count = src.count; + memcpy(data, src.data, size); + + return *this; + } + + DynamicBuffer& operator=(DynamicBuffer&& src) { + std::cout << "DynamicBuffer::move" << std::endl; + if (data) { + free(data); + } + + data = src.data; + count = src.count; + + src.data = nullptr; + src.count = 0; + + return *this; + } + + int *data; + size_t count; +}; + +/* Add an element to a new or existing buffer */ +void add_elem(flecs::entity e, int value) { + DynamicBuffer *ptr = e.get_mut(); + + ptr->count ++; + ptr->data = static_cast(realloc(ptr->data, ptr->count * sizeof(int))); + ptr->data[ptr->count - 1] = value; +} + +/* Remove element from buffer */ +void remove_elem(flecs::entity e, size_t elem) { + DynamicBuffer *ptr = e.get_mut(); + + size_t last = ptr->count - 1; + + if (last >= elem) { + if (last - elem) { + ptr->data[elem] = ptr->data[last]; + } + + ptr->count --; + } +} + +/* Get element from a buffer */ +int* get_elem(flecs::entity e, size_t elem) { + const DynamicBuffer *ptr = e.get(); + + if (ptr && (ptr->count > elem)) { + return &ptr->data[elem]; + } else { + return NULL; + } +} + +int main(int argc, char *argv[]) { + flecs::world ecs(argc, argv); + + auto e = ecs.entity(); + + /* Add 3 elements to the buffer. The first add will add the DynamicBuffer + * element to the entity. */ + add_elem(e, 10); + add_elem(e, 20); + add_elem(e, 30); + + std::cout << "Elem 1 = " << *get_elem(e, 1) << std::endl; + + /* Remove element. This will move the last element from the buffer to the + * removed element. */ + remove_elem(e, 1); + + std::cout << "Elem 1 = " << *get_elem(e, 1) << " (after remove)" << std::endl; + + /* Add component. This causes the entity to move between tables, and will + * invoke DynamicComponent::move to copy the component value from the src to + * the dst table. This also invokes DynamicComponent::ctor to construct the + * component in the dst table. */ + e.add(); + + /* World gets cleaned up, which invokes DynamicComponent::dtor. */ +}