diff --git a/include/ikarus/objects/blueprint.h b/include/ikarus/objects/blueprint.h index ed35c89..85f40eb 100644 --- a/include/ikarus/objects/blueprint.h +++ b/include/ikarus/objects/blueprint.h @@ -28,55 +28,13 @@ struct IkarusBlueprint; /// \remark Must be freed using #ikarus_free. IKA_API IkarusBlueprint * ikarus_blueprint_create(struct IkarusProject * project, char const * name); -/// \brief Creates a blueprint from an entity. -/// \details The created blueprint will have the same properties as the entity. -/// \param entity The entity to create the blueprint from. -/// \pre \li Must not be null. -/// \param link_entity If true, the entity will be linked to the blueprint. If not they will remain separate. -/// \param name The name of the blueprint. Must not be empty. -/// \pre \li Must not be null. -/// \pre \li Must not be empty. -/// \return The created blueprint or null if an error occurs. -/// \remark Must be freed using #ikarus_free. -IKA_API IkarusBlueprint * ikarus_blueprint_create_from_entity( - struct IkarusEntity * entity, bool link_entity, char const * name -); - -/// \brief Copies a blueprint. -/// \details Creates a deep copy of the blueprint including all of its properties. -/// \param blueprint The blueprint to copy. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \param name The name of the blueprint. -/// \pre \li Must not be null. -/// \pre \li Must not be empty. -/// \return The created blueprint or null if an error occurs. -/// \remark Linked entities won't be copied. -/// \remark Must be freed using #ikarus_free. -IKA_API IkarusBlueprint * ikarus_blueprint_copy(IkarusBlueprint const * blueprint, char const * name); - -/// \brief Deletes a blueprint. +/// \brief Deletes & frees a blueprint. /// \param blueprint The blueprint to delete. /// \pre \li Must not be null. /// \pre \li Must exist. /// \remark The blueprint must not be accessed after deletion. IKA_API void ikarus_blueprint_delete(IkarusBlueprint * blueprint); -/// \brief Gets the project of a blueprint. -/// \param blueprint The blueprint to get the project of. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \return The project of the blueprint or null if an error occurs. -IKA_API struct IkarusProject * ikarus_blueprint_get_project(IkarusBlueprint const * blueprint); - -/// \brief Gets the name of a blueprint. -/// \param blueprint The blueprint to get the name of. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \return The name of the blueprint or null if an error occurs. -/// \remark The returned pointer is valid until the blueprint is freed but may be invalidated by other operations. -IKA_API char const * ikarus_blueprint_get_name(IkarusBlueprint const * blueprint); - /// \brief Gets the number of properties of a blueprint. /// \param blueprint The blueprint to get the number of properties of. /// \pre \li Must not be null. @@ -113,26 +71,13 @@ IKA_API void ikarus_blueprint_get_linked_entities( IkarusBlueprint const * blueprint, struct IkarusEntity ** entities_out, size_t entities_out_size ); -/// \brief Sets the name of a blueprint. -/// \param blueprint The blueprint to set the name of. +/// \brief Casts a blueprint to an object. +/// \param blueprint The blueprint to cast. /// \pre \li Must not be null. /// \pre \li Must exist. -/// \param new_name The new name of the blueprint. -/// \pre \li Must not be null. -/// \pre \li Must not be empty. -IKA_API void ikarus_blueprint_set_name(IkarusBlueprint * blueprint, char const * new_name); - -/// \brief Compares two blueprints. -/// \param left The left blueprint to compare. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \param right The right blueprint to compare. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \return True if the two blueprints are equal, false otherwise. -/// \remark This neither performs a pointer comparison nor a deep comparison. When we say "equal" we mean that the two -/// blueprints reference the same blueprint in the same project. -IKA_API bool ikarus_blueprint_is_equal(IkarusBlueprint const * left, IkarusBlueprint const * right); +/// \return The blueprint represented as an object or null if an error occurs. +/// \remark This operation is guaranteed to be very fast and is intended to be used frequently. +IKA_API struct IkarusObject * ikarus_blueprint_to_object(IkarusBlueprint const * blueprint); IKARUS_END_HEADER diff --git a/include/ikarus/objects/entity.h b/include/ikarus/objects/entity.h index e73e18d..813adea 100644 --- a/include/ikarus/objects/entity.h +++ b/include/ikarus/objects/entity.h @@ -58,25 +58,6 @@ IKA_API IkarusEntity * ikarus_entity_create( size_t blueprints_count ); -/// \brief Copies an entity. -/// \details Creates a deep copy of the entity including all of its properties & associated values. -/// \param entity The entity to copy. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \param parent The parent folder of the entity. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \param position The position of the entity in the parent folder. \see #FolderPosition -/// \pre \li Must be within bounds for the parent folder. -/// \param name The name of the entity. -/// \pre \li Must not be null. -/// \pre \li Must not be empty. -/// \return The created entity or null if an error occurs. -/// \remark Must be freed using #ikarus_free. -IKA_API IkarusEntity * ikarus_entity_copy( - struct IkarusEntity * entity, struct IkarusEntityFolder * parent, size_t position, char const * name -); - /// \brief Deletes an entity. /// \param entity The entity to delete. /// \pre \li Must not be null. @@ -117,43 +98,6 @@ IKA_API void ikarus_entity_link_to_blueprint(IkarusEntity * entity, struct Ikaru /// \remark No-op if the entity is not linked to the blueprint. IKA_API void ikarus_entity_unlink_from_blueprint(IkarusEntity * entity, struct IkarusBlueprint * blueprint); -/// \brief Gets the project of an entity. -/// \param entity The entity to get the project of. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \return The project of the entity or null if an error occurs. -IKA_API struct IkarusProject * ikarus_entity_get_project(IkarusEntity const * entity); - -/// \brief Gets the parent folder of an entity. -/// \param entity The entity to get the parent folder of. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \return The parent folder of the entity or null if an error occurs. -IKA_API struct IkarusEntityFolder * ikarus_entity_get_parent(IkarusEntity const * entity); - -/// \brief Gets the position of an entity within its parent folder. -/// \param entity The entity to get the position of. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \return The position of the entity or undefined if an error occurs. -IKA_API size_t ikarus_entity_get_position(IkarusEntity const * entity); - -/// \brief Gets the name of an entity. -/// \param entity The entity to get the name of. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \return The name of the entity or null if an error occurs. -/// \remark The returned pointer is valid until the entity is freed but may be invalidated by other operations. -IKA_API char const * ikarus_entity_get_name(IkarusEntity const * entity); - -/// \brief Gets the property root folder of an entity. -/// \param entity The entity to get the root folder of. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \return The root folder of all properties of the entity or null if an error occurs. -/// \remark Must be freed using #ikarus_free. -IKA_API struct IkarusPropertyFolder * ikarus_entity_get_property_root_folder(IkarusEntity const * entity); - /// \brief Gets the number of properties of an entity. /// \param entity The entity to get the number of properties of. /// \pre \li Must not be null. @@ -184,36 +128,6 @@ IKA_API void ikarus_entity_get_properties( /// \remark Must be freed using #ikarus_free. IKA_API struct IkarusEntityValue * get_value(IkarusEntity const * entity, struct IkarusProperty const * property); -/// \brief Sets the parent folder of an entity. -/// \param entity The entity to set the parent folder of. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \param new_parent The new parent folder of the entity. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \param new_position The new position of the entity in the parent folder. \see #FolderPosition -/// \pre \li Must be within bounds for the parent folder. -/// \remark This adjusts the positions of old and new siblings. -IKA_API void ikarus_entity_set_parent(IkarusEntity * entity, struct IkarusEntityFolder * new_parent, size_t new_position); - -/// \brief Sets the position of an entity within its parent folder. -/// \param entity The entity to set the position of. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \param new_position The new position of the entity. \see #FolderPosition -/// \pre \li Must be within bounds for the parent folder. -/// \remark This adjusts the positions of siblings. -IKA_API void ikarus_entity_set_position(IkarusEntity * entity, size_t new_position); - -/// \brief Sets the name of an entity. -/// \param entity The entity to set the name of. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \param new_name The new name of the entity. -/// \pre \li Must not be null. -/// \pre \li Must not be empty. -IKA_API void ikarus_entity_set_name(IkarusEntity * entity, char const * new_name); - /// \brief Sets the value of a property of an entity. /// \param entity The entity to set the value of. /// \pre \li Must not be null. @@ -231,17 +145,13 @@ IKA_API void ikarus_entity_set_value( IkarusEntity * entity, struct IkarusProperty const * property, struct IkarusValue * value, bool validate_settings ); -/// \brief Compares two entities. -/// \param left The left entity to compare. +/// \brief Casts an entity to an object. +/// \param entity The entity to cast. /// \pre \li Must not be null. /// \pre \li Must exist. -/// \param right The right entity to compare. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \return True if the two entities are equal, false otherwise. -/// \remark This neither performs a pointer comparison nor a deep comparison. When we say "equal" we mean that the two -/// entities reference the same entity in the same project. -IKA_API bool ikarus_entity_is_equal(IkarusEntity const * left, IkarusEntity const * right); +/// \return The entity represented as an object or null if an error occurs. +/// \remark This operation is guaranteed to be very fast and is intended to be used frequently. +IKA_API struct IkarusObject * ikarus_entity_to_object(IkarusEntity const * entity); IKARUS_END_HEADER diff --git a/include/ikarus/objects/property.h b/include/ikarus/objects/property.h index 10258df..a62a821 100644 --- a/include/ikarus/objects/property.h +++ b/include/ikarus/objects/property.h @@ -73,23 +73,6 @@ IKA_API struct IkarusProperty * ikarus_property_create( struct IkarusPropertyTypeInfo * property_info ); -/// \brief Copies a property. -/// \details Creates a deep copy of the property including all of its settings and associated values. -/// \param property The property to copy. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \param source The source to copy the property to. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \param name The name of the property. -/// \pre \li Must not be null. -/// \pre \li Must not be empty. -/// \return The created property or null if an error occurs. -/// \remark Must be freed using #ikarus_free. -IKA_API struct IkarusProperty * ikarus_property_copy( - struct IkarusProperty * property, struct IkarusPropertySource * source, char const * name -); - /// \brief Deletes a property. /// \param property The property to delete. /// \pre \li Must not be null. @@ -97,21 +80,6 @@ IKA_API struct IkarusProperty * ikarus_property_copy( /// \remark The property must not be accessed after deletion. IKA_API void ikarus_property_delete(struct IkarusProperty * property); -/// \brief Gets the project of a property. -/// \param property The property to get the project of. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \return The project of the property or null if an error occurs. -IKA_API struct IkarusProject * ikarus_property_get_project(IkarusProperty const * property); - -/// \brief Gets the name of a property. -/// \param property The property to get the name of. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \return The name of the property or null if an error occurs. -/// \remark The returned pointer is valid until the property is freed but may be invalidated by other operations. -IKA_API char const * ikarus_property_get_name(IkarusProperty const * property); - /// \brief Gets the type info of a property. /// \param property The property to get the type info of. /// \pre \li Must not be null. @@ -136,15 +104,6 @@ IKA_API struct IkarusPropertySource * ikarus_property_get_source(IkarusProperty /// \remark Must be freed using #ikarus_free. IKA_API struct IkarusValue * ikarus_property_get_default_value(IkarusProperty const * property); -/// \brief Sets the name of a property. -/// \param property The property to set the name of. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \param new_name The new name of the property. -/// \pre \li Must not be null. -/// \pre \li Must not be empty. -IKA_API void ikarus_property_set_name(IkarusProperty * property, char const * new_name); - /// \brief Sets the type info of a property and resets all values to the new default value. /// \param property The property to set the type info of. /// \pre \li Must not be null. @@ -156,17 +115,13 @@ IKA_API void ikarus_property_set_type_info( IkarusProperty * property, struct IkarusPropertyTypeInfo new_type_info, bool attempt_conversion ); -/// \brief Compares two properties. -/// \param left The left property to compare. +/// \brief Casts a property to an object. +/// \param property The property to cast. /// \pre \li Must not be null. /// \pre \li Must exist. -/// \param right The right property to compare. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \return True if the two properties are equal, false otherwise. -/// \remark This neither performs a pointer comparison nor a deep comparison. When we say "equal" we mean that the two -/// properties reference the same property in the same project. -IKA_API bool ikarus_property_is_equal(IkarusProperty const * left, IkarusProperty const * right); +/// \return The property represented as an object or null if an error occurs. +/// \remark This operation is guaranteed to be very fast and is intended to be used frequently. +IKA_API struct IkarusObject * ikarus_property_to_object(IkarusProperty const * property); IKARUS_END_HEADER diff --git a/src/objects/blueprint.cpp b/src/objects/blueprint.cpp new file mode 100644 index 0000000..3d1c997 --- /dev/null +++ b/src/objects/blueprint.cpp @@ -0,0 +1,135 @@ +#include "blueprint.hpp" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +IkarusBlueprint * ikarus_blueprint_create(struct IkarusProject * project, char const * name) { + LOG_INFO("creating new blueprint"); + + if (project == nullptr) { + LOG_ERROR("project is nullptr"); + return nullptr; + } + + LOG_VERBOSE("project={}; name={}", project->path.c_str(), name); + + if (name == nullptr) { + LOG_ERROR("name is nullptr"); + return nullptr; + } + + if (cppbase::is_empty_or_blank(name)) { + LOG_ERROR("name is empty or blank"); + return nullptr; + } + + VTRYRV(auto id, nullptr, project->db->transact([name](auto * db) -> cppbase::Result { + LOG_VERBOSE("creating blueprint in objects table"); + + TRY(db->execute( + "INSERT INTO `objects` (`object_type`, `name`) VALUES(?, ?);", static_cast(IkarusObjectType_Blueprint), name + )); + + auto id = db->last_insert_rowid(); + + LOG_VERBOSE("blueprint is {}", id); + + LOG_VERBOSE("inserting blueprint into blueprints table"); + + TRY(db->execute("INSERT INTO `blueprints`(`id`) VALUES(?);", id)); + + return cppbase::ok(id); + })); + + LOG_VERBOSE("successfully created blueprint"); + + return new IkarusBlueprint{project, id}; +} + +void ikarus_blueprint_delete(IkarusBlueprint * blueprint) { + LOG_INFO("deleting blueprint"); + + if (blueprint == nullptr) { + LOG_ERROR("blueprint is nullptr"); + return; + } + + LOG_VERBOSE("blueprint={}", blueprint->id); + + if (auto res = blueprint->project->db->execute("DELETE FROM `objects` WHERE `id` = ?", blueprint->id); res.is_err()) { + LOG_ERROR("failed to delete blueprint {} from objects table: {}", blueprint->id, res.unwrap_error()); + return; + } + + LOG_VERBOSE("blueprint was successfully deleted from database, freeing pointer"); + + delete blueprint; + + LOG_VERBOSE("successfully deleted blueprint"); +} + +size_t ikarus_blueprint_get_property_count(IkarusBlueprint const * blueprint) { + LOG_VERBOSE("fetching blueprint property count"); + + if (blueprint == nullptr) { + LOG_ERROR("blueprint is nullptr"); + return 0; + } + + LOG_VERBOSE("blueprint={}", blueprint->id); + + VTRYRV( + auto count, + 0, + blueprint->project->db->query_one( + "SELECT COUNT(*) FROM `blueprint_properties` WHERE `blueprint_id` = ?;", blueprint->id + ) + ); + + return static_cast(count); +} + +void ikarus_blueprint_get_properties( + IkarusBlueprint const * blueprint, struct IkarusProperty ** properties_out, size_t properties_out_size +) { + LOG_VERBOSE("fetching blueprint properties"); + + if (blueprint == nullptr) { + LOG_ERROR("blueprint is nullptr"); + return; + } + + if (properties_out == nullptr) { + LOG_ERROR("properties_out is nullptr"); + return; + } + + LOG_VERBOSE("blueprint={}; properties_out_size={}", blueprint->id, properties_out_size); + + IkarusId ids[properties_out_size]; + + if (auto res = blueprint->project->db->query_many_buffered( + "SELECT `id` FROM `properties` WHERE `source` = ?", static_cast(ids), properties_out_size, blueprint->id + ); + res.is_err()) { + LOG_ERROR("failed to fetch blueprint property ids: {}", res.unwrap_error()); + return; + } + + for (size_t i = 0; i < properties_out_size; ++i) { + /// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + properties_out[i] = new IkarusProperty{blueprint->project, ids[i]}; + } + + LOG_VERBOSE("successfully fetched blueprint properties"); +} diff --git a/src/objects/blueprint.hpp b/src/objects/blueprint.hpp index 5d5e554..f904074 100644 --- a/src/objects/blueprint.hpp +++ b/src/objects/blueprint.hpp @@ -1,9 +1,9 @@ #pragma once -#include +#include /// \private -struct IkarusBlueprint { - struct IkarusProject * project; - IkarusId id; +struct IkarusBlueprint : public IkarusObject { + inline IkarusBlueprint(struct IkarusProject * project, IkarusId id): + IkarusObject{project, id} {} }; diff --git a/src/objects/entity.hpp b/src/objects/entity.hpp index 71e3cea..91b3d3e 100644 --- a/src/objects/entity.hpp +++ b/src/objects/entity.hpp @@ -1,9 +1,6 @@ #pragma once -#include +#include /// \private -struct IkarusEntity { - struct IkarusProject * project; - IkarusId id; -}; +struct IkarusEntity : public IkarusObject {}; diff --git a/src/objects/object.hpp b/src/objects/object.hpp index 668cd65..e95c3e0 100644 --- a/src/objects/object.hpp +++ b/src/objects/object.hpp @@ -2,12 +2,13 @@ #include -#include -#include -#include +#include -union IkarusObject { - IkarusBlueprint blueprint; - IkarusEntity entity; - IkarusProperty property; +struct IkarusObject { + struct IkarusProject * project; + IkarusId id; + + inline IkarusObject(struct IkarusProject * project, IkarusId id): + project{project}, + id{id} {} }; diff --git a/src/objects/property.hpp b/src/objects/property.hpp index 5132207..b59d7dd 100644 --- a/src/objects/property.hpp +++ b/src/objects/property.hpp @@ -1,9 +1,9 @@ #pragma once -#include +#include /// \private -struct IkarusProperty { - struct IkarusProject * project; - IkarusId id; +struct IkarusProperty : public IkarusObject { + inline IkarusProperty(struct IkarusProject * project, IkarusId id): + IkarusObject{project, id} {} }; diff --git a/src/persistence/migrations/m1_initial_layout.sql b/src/persistence/migrations/m1_initial_layout.sql index 60ecc47..7b49cb6 100644 --- a/src/persistence/migrations/m1_initial_layout.sql +++ b/src/persistence/migrations/m1_initial_layout.sql @@ -2,8 +2,7 @@ CREATE TABLE `objects` ( `do_not_access_rowid_alias` INTEGER PRIMARY KEY, `object_type` INT NOT NULL, - `id` INT GENERATED ALWAYS AS (`do_not_access_rowid_alias` | (`object_type` << 56) - ) VIRTUAL, + `id` INT GENERATED ALWAYS AS (`do_not_access_rowid_alias` | (`object_type` << 56)) VIRTUAL, `name` TEXT NOT NULL, `information` TEXT NOT NULL ) STRICT; @@ -47,6 +46,7 @@ CREATE TABLE `entity_blueprints` PRIMARY KEY (`entity`), UNIQUE (`entity`, `blueprint`), + FOREIGN KEY (`entity`) REFERENCES `entities` (`id`) ON DELETE CASCADE, FOREIGN KEY (`blueprint`) REFERENCES `blueprints` (`id`) ON DELETE CASCADE ) WITHOUT ROWID, STRICT; @@ -58,12 +58,15 @@ CREATE TABLE `properties` `type` INT NOT NULL, `default_value` TEXT NOT NULL, `settings` TEXT NOT NULL, + `source` INT NOT NULL, PRIMARY KEY (`id`), - FOREIGN KEY (`id`) REFERENCES `objects` (`id`) ON DELETE CASCADE + FOREIGN KEY (`id`) REFERENCES `objects` (`id`) ON DELETE CASCADE, + FOREIGN KEY (`source`) REFERENCES `objects` (`id`) ON DELETE CASCADE ) WITHOUT ROWID, STRICT; CREATE INDEX `properties_type` ON `properties` (`type`); +CREATE INDEX `properties_source` ON `properties` (`source`); CREATE VIRTUAL TABLE `property_default_value_fts` USING fts5