implement remaining logic

Signed-off-by: Folling <mail@folling.io>
This commit is contained in:
folling 2024-01-03 17:14:26 +01:00 committed by Folling
parent e17e346768
commit bac85e87c8
Signed by: folling
SSH key fingerprint: SHA256:S9qEx5WCFFLK49tE/LKnKuJYM5sw+++Dn6qJbbyxnCY
41 changed files with 1393 additions and 408 deletions

View file

@ -1,7 +1,7 @@
Checks: >- Checks: >-
-*, -*,
bugprone-*, -bugprone-lambda-function-name, bugprone-*, -bugprone-lambda-function-name,
cppcoreguidelines-*, -cppcoreguidelines-owning-memory, -cppcoreguidelines-non-private-member-variables-in-classes, cppcoreguidelines-*, -cppcoreguidelines-macro-usage, -cppcoreguidelines-owning-memory, -cppcoreguidelines-non-private-member-variables-in-classes,
clang-analyzer-*, clang-analyzer-*,
google-*, -google-readability-todo, google-*, -google-readability-todo,
modernize-*, -modernize-use-trailing-return-type, modernize-*, -modernize-use-trailing-return-type,
@ -9,9 +9,9 @@ Checks: >-
portability-*, portability-*,
readability-*, -readability-redundant-access-specifiers readability-*, -readability-redundant-access-specifiers
CheckOptions: CheckOptions:
readability-identifier-length.IgnoredParameterNames: '^(db|rc|id)$' readability-identifier-length.IgnoredParameterNames: '^(db|rc|id|ec)$'
readability-identifier-length.IgnoredLoopCounterNames: '^[ij]$' readability-identifier-length.IgnoredLoopCounterNames: '^[ij]$'
readability-identifier-length.IgnoredVariableNames: '^(db|rc|id)$' readability-identifier-length.IgnoredVariableNames: '^(db|rc|id|ec)$'
cppcoreguidelines-avoid-do-while.IgnoreMacros: Yes cppcoreguidelines-avoid-do-while.IgnoreMacros: Yes
HeaderFileExtensions: HeaderFileExtensions:
- h - h

View file

@ -14,7 +14,7 @@ add_subdirectory(vendor)
add_subdirectory(include) add_subdirectory(include)
add_subdirectory(src) add_subdirectory(src)
find_package(Boost REQUIRED) find_package(Boost COMPONENTS system filesystem REQUIRED)
add_library( add_library(
libikarus SHARED libikarus SHARED

View file

@ -4,6 +4,7 @@
/// \author Folling <folling@ikarus.world> /// \author Folling <folling@ikarus.world>
#include <ikarus/macros.h> #include <ikarus/macros.h>
#include <ikarus/stdtypes.h>
/// \addtogroup errors Errors /// \addtogroup errors Errors
/// \brief Error handling within libikarus /// \brief Error handling within libikarus
@ -24,94 +25,81 @@ IKARUS_BEGIN_HEADER
enum IkarusErrorInfo { enum IkarusErrorInfo {
/// \brief No error occurred. /// \brief No error occurred.
IkarusErrorInfo_None = 0x0, IkarusErrorInfo_None = 0x0,
/// \brief The error was caused by the client.
IkarusErrorInfo_Client = 0x10100000,
/// \brief The error was caused by a dependency (e.g. boost) of libikarus.
IkarusErrorInfo_Dependency = 0x10200000,
/// \brief The error was caused by the filesystem.
IkarusErrorInfo_Filesystem = 0x10300000,
/// \brief The error was caused by the database.
IkarusErrorInfo_Database = 0x10400000,
/// \brief The error was caused by the underlying OS.
IkarusErrorInfo_OS = 0x10500000,
/// \brief The error was caused by libikarus itself.
IkarusErrorInfo_LibIkarus = 0x10600000,
/// \brief The client misused the API. /// \brief The client misused the API.
/// Example: Accessing a resource that does not exist. /// Example: Accessing a resource that does not exist.
IkarusErrorInfo_Client_Misuse = 0x10100001, IkarusErrorInfo_Client_Misuse = 0x01000001,
/// \brief The client provided a null value for a parameter that must not be null. /// \brief The client provided a null value for a parameter that must not be null.
/// Example: Passing null for `ikarus_project_get_name` /// Example: Passing null for `ikarus_project_get_name`
IkarusErrorInfo_Client_InvalidNull = 0x10100002, IkarusErrorInfo_Client_InvalidNull = 0x01000002,
/// \brief The client provided an index that was out of bounds for some array. /// \brief The client provided an index that was out of bounds for some array.
/// Example: Passing the index 3 for an `IkarusToggleValue` with size 3. /// Example: Passing the index 3 for an `IkarusToggleValue` with size 3.
IkarusErrorInfo_Client_IndexOutOfBounds = 0x10100003, IkarusErrorInfo_Client_IndexOutOfBounds = 0x01000003,
/// \brief The client provided a numeric value that was out of bounds /// \brief The client provided a numeric value that was out of bounds
/// Example: Passing the value 2^32 to an i32 (might be passed as a string). /// Example: Passing the value 2^32 to an i32 (might be passed as a string).
IkarusErrorInfo_Client_ValueOutOfBounds = 0x10100004, IkarusErrorInfo_Client_ValueOutOfBounds = 0x01000004,
/// \brief The client provided invalid input that doesn't fit in any of the other categories. /// \brief The client provided invalid input that doesn't fit in any of the other categories.
/// Example: Passing an empty/blank string for a string that must be non-empty/-blank. /// Example: Passing an empty/blank string for a string that must be non-empty/-blank.
IkarusErrorInfo_Client_InvalidInput = 0x10100005, IkarusErrorInfo_Client_InvalidInput = 0x01000005,
/// \brief The client provided valid data in an invalid format. /// \brief The client provided valid data in an invalid format.
/// Example: Passing a malformed JSON string. /// Example: Passing a malformed JSON string.
IkarusErrorInfo_Client_InvalidFormat = 0x10100006, IkarusErrorInfo_Client_InvalidFormat = 0x01000006,
/// \brief The client violated a constraint. /// \brief The client violated a constraint.
/// \details This error is most likely caused by endusers. /// \details This error is most likely caused by endusers.
/// Example: A user tries to set the age of a character to an value outside of their specified range. /// Example: A user tries to set the age of a character to an value outside of their specified range.
IkarusErrorInfo_Client_ConstraintViolated = 0x10100007, IkarusErrorInfo_Client_ConstraintViolated = 0x10000007,
// 0x02 reserved for dependency errors
/// \brief A file was not found.
IkarusErrorInfo_Filesystem_NotFound = 0x10300001,
/// \brief A file or directory already exists. /// \brief A file or directory already exists.
IkarusErrorInfo_Filesystem_AlreadyExists = 0x10300002, IkarusErrorInfo_Filesystem_AccessIssue = 0x03000001,
/// \brief A file was not found.
IkarusErrorInfo_Filesystem_NotFound = 0x03000002,
/// \brief A file or directory already exists.
IkarusErrorInfo_Filesystem_AlreadyExists = 0x03000003,
/// \brief Missing permissions to access a file or directory. /// \brief Missing permissions to access a file or directory.
IkarusErrorInfo_Filesystem_MissingPermissions = 0x10300003, IkarusErrorInfo_Filesystem_MissingPermissions = 0x03000004,
/// \brief Insufficient space to perform an operation. /// \brief Insufficient space to perform an operation.
IkarusErrorInfo_Filesystem_InsufficientSpace = 0x10300004, IkarusErrorInfo_Filesystem_InsufficientSpace = 0x03000005,
/// \brief A path is invalid. /// \brief A path is invalid.
IkarusErrorInfo_Filesystem_InvalidPath = 0x10300005, IkarusErrorInfo_Filesystem_InvalidPath = 0x03000006,
/// \brief A database connection failed. /// \brief A database connection failed.
IkarusErrorInfo_Database_ConnectionFailed = 0x10400001, IkarusErrorInfo_Database_ConnectionFailed = 0x04000001,
/// \brief A database query failed. /// \brief A database query failed.
IkarusErrorInfo_Database_QueryFailed = 0x10400002, IkarusErrorInfo_Database_QueryFailed = 0x04000002,
/// \brief A database migration failed. /// \brief A database migration failed.
IkarusErrorInfo_Database_MigrationFailed = 0x10400003, IkarusErrorInfo_Database_MigrationFailed = 0x04000003,
/// \brief A database is in an invalid state. This indicates a corrupt project. /// \brief A database is in an invalid state. This indicates a corrupt project.
/// Example: An entity is linked to a non-existant blueprint. /// Example: An entity is linked to a non-existant blueprint.
IkarusErrorInfo_Database_InvalidState = 0x10400004, IkarusErrorInfo_Database_InvalidState = 0x04000004,
/// \brief A system call failed. /// \brief A system call failed.
IkarusErrorInfo_OS_SystemCallFailed = 0x10500001, IkarusErrorInfo_OS_SystemCallFailed = 0x05000001,
/// \brief A system call returned an invalid value. /// \brief A system call returned an invalid value.
IkarusErrorInfo_OS_InvalidReturnValue = 0x10500002, IkarusErrorInfo_OS_InvalidReturnValue = 0x05000002,
/// \brief An OOM error occurred. /// \brief An OOM error occurred.
IkarusErrorInfo_OS_InsufficientMemory = 0x10500003, IkarusErrorInfo_OS_InsufficientMemory = 0x05000003,
/// \brief A datapoint within ikarus is invalid for the current state of the system. /// \brief A datapoint within ikarus is invalid for the current state of the system.
/// \details This differs from IkarusErrorInfo_Database_InvalidState in that the latter implies the database itself holds invalid state,
/// whereas the former may imply that the state is ephemeral, e.g. data within a function.
/// Example: The name of an object is found to be invalid UTF8. /// Example: The name of an object is found to be invalid UTF8.
IkarusErrorInfo_LibIkarus_InvalidState = 0x20030001, IkarusErrorInfo_LibIkarus_InvalidState = 0x06000001,
/// \brief libikarus is unable to perform a certain operation that should succeed.
IkarusErrorInfo_LibIkarus_CannotPerformOperation = 0x06000002,
/// \brief libikarus is unable to perform a certain operation within a given timeframe. /// \brief libikarus is unable to perform a certain operation within a given timeframe.
/// Example: A query takes longer than the timeout. /// Example: A query takes longer than the timeout.
IkarusErrorInfo_LibIkarus_Timeout = 0x20030003, IkarusErrorInfo_LibIkarus_Timeout = 0x06000003,
}; };
/// \brief The data limits for an error. size_t const IKARUS_ERROR_DATA_MAX_MESSAGE_LIMIT = 128;
enum IkarusErrorDataLimit {
/// \brief The maximum number of error infos that can be stored in an error.
IkarusErrorDataLimit_MaxErrorInfos = 8,
/// \brief The maximum length of an error message.
IkarusErrorDataLimit_MaxMessageLength = 128,
};
/// \brief The data stored for an error /// \brief The data stored for an error
struct IkarusErrorData { struct IkarusErrorData {
/// \brief The scope of the error. /// \brief The error type
/// \details This array may at most hold #IkarusErrorDataLimit_MaxErrorInfos elements. IkarusErrorInfo info;
/// The first occurrence of #IkarusErrorInfo_None signifies the end of the array. If this happens at idx x== 0, no error occurred.
IkarusErrorInfo infos[IkarusErrorDataLimit_MaxErrorInfos];
char message[IkarusErrorDataLimit_MaxMessageLength]; char message[IKARUS_ERROR_DATA_MAX_MESSAGE_LIMIT];
}; };
/// \brief Gets the name of an error info. /// \brief Gets the name of an error info.
@ -128,11 +116,6 @@ IKA_API bool ikarus_error_data_is_success(IkarusErrorData const * data);
/// \param data The error data to check. /// \param data The error data to check.
/// \return True if the error data is an error, false otherwise. /// \return True if the error data is an error, false otherwise.
IKA_API bool ikarus_error_data_is_error(IkarusErrorData const * data); IKA_API bool ikarus_error_data_is_error(IkarusErrorData const * data);
/// \brief Formats the error data in a reasonable but unspecified way.
/// \param data The error data to format.
/// \return The formatted error data.
/// \remark Ownership of the returned pointer is passed to the user and must be freed at their leisure using ikarus_free.
IKA_API char const * ikarus_error_data_pretty_format(IkarusErrorData const * data);
IKARUS_END_HEADER IKARUS_END_HEADER

View file

@ -89,6 +89,30 @@ IKA_API void ikarus_entity_link_to_blueprint(IkarusEntity * entity, struct Ikaru
/// \remark No-op if the entity is not linked to the blueprint. /// \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, IkarusErrorData * error_out); IKA_API void ikarus_entity_unlink_from_blueprint(IkarusEntity * entity, struct IkarusBlueprint * blueprint, IkarusErrorData * error_out);
/// \brief Gets the blueprints an entity is linked to.
/// \param entity The entity to get the blueprints of.
/// \pre \li Must not be null.
/// \pre \li Must exist.
/// \param blueprints_out The buffer to write the blueprints to.
/// \pre \li Must not be null.
/// \param blueprints_out_size The size of the buffer.
/// \param error_out \see errors.h
/// \see ikarus_entity_get_linked_blueprint_count
IKA_API void ikarus_entity_get_linked_blueprints(
IkarusEntity const * entity,
struct IkarusBlueprint ** blueprints_out,
size_t blueprints_out_size,
IkarusErrorData * error_out
);
/// \brief Gets the number of blueprints an entity is linked to.
/// \param entity The entity to get the number of blueprints of.
/// \pre \li Must not be null.
/// \pre \li Must exist.
/// \param error_out \see errors.h
/// \return The number of blueprints or undefined if an error occurs.
IKA_API size_t ikarus_entity_get_linked_blueprint_count(IkarusEntity const * entity, IkarusErrorData * error_out);
/// \brief Checks if an entity has a specific property. /// \brief Checks if an entity has a specific property.
/// \param entity The entity to check. /// \param entity The entity to check.
/// \pre \li Must not be null. /// \pre \li Must not be null.
@ -97,17 +121,9 @@ IKA_API void ikarus_entity_unlink_from_blueprint(IkarusEntity * entity, struct I
/// \pre \li Must not be null. /// \pre \li Must not be null.
/// \pre \li Must exist. /// \pre \li Must exist.
/// \param error_out \see errors.h /// \param error_out \see errors.h
/// \return True if the entity has the property, false otherwise. /// \return False if an error occurs or the entity does not have the property, true otherwise.
IKA_API bool ikarus_entity_has_property(IkarusEntity const * entity, struct IkarusProperty const * property, IkarusErrorData * error_out); IKA_API bool ikarus_entity_has_property(IkarusEntity const * entity, struct IkarusProperty const * property, IkarusErrorData * error_out);
/// \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.
/// \pre \li Must exist.
/// \param error_out \see errors.h
/// \return The number of properties or undefined if an error occurs.
IKA_API size_t ikarus_entity_get_property_count(IkarusEntity const * entity, IkarusErrorData * error_out);
/// \brief Gets the properties of an entity. /// \brief Gets the properties of an entity.
/// \param entity The entity to get the properties of. /// \param entity The entity to get the properties of.
/// \pre \li Must not be null. /// \pre \li Must not be null.
@ -116,6 +132,7 @@ IKA_API size_t ikarus_entity_get_property_count(IkarusEntity const * entity, Ika
/// \pre \li Must not be null. /// \pre \li Must not be null.
/// \param error_out \see errors.h /// \param error_out \see errors.h
/// \param properties_out_size The size of the buffer. /// \param properties_out_size The size of the buffer.
/// \see ikarus_entity_get_property_count
IKA_API void ikarus_entity_get_properties( IKA_API void ikarus_entity_get_properties(
IkarusEntity const * entity, IkarusEntity const * entity,
struct IkarusProperty ** properties_out, struct IkarusProperty ** properties_out,
@ -123,9 +140,16 @@ IKA_API void ikarus_entity_get_properties(
IkarusErrorData * error_out IkarusErrorData * error_out
); );
/// \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.
/// \pre \li Must exist.
/// \param error_out \see errors.h
/// \return The number of properties or undefined if an error occurs.
IKA_API size_t ikarus_entity_get_property_count(IkarusEntity const * entity, IkarusErrorData * error_out);
/// \brief Gets the value of a property of an entity. /// \brief Gets the value of a property of an entity.
/// \details If the entity has never set the value of the property, the default value is returned (which may be /// \details If the entity has never set the value of the property, the default value is returned (which may be undefined).
/// undefined).
/// \param entity The entity to get the value of. /// \param entity The entity to get the value of.
/// \pre \li Must not be null. /// \pre \li Must not be null.
/// \pre \li Must exist. /// \pre \li Must exist.
@ -134,9 +158,8 @@ IKA_API void ikarus_entity_get_properties(
/// \pre \li Must exist. /// \pre \li Must exist.
/// \param error_out \see errors.h /// \param error_out \see errors.h
/// \return The value of the property or null if the entity does not have the property or an error occurs. /// \return The value of the property or null if the entity does not have the property or an error occurs.
/// \remark Must be freed using /// \remark Must be freed using #ikarus_free.
/// #ikarus_free. IKA_API struct IkarusEntityPropertyValue *
IKA_API struct IkarusEntityValue *
ikarus_entity_get_value(IkarusEntity const * entity, struct IkarusProperty const * property, IkarusErrorData * error_out); ikarus_entity_get_value(IkarusEntity const * entity, struct IkarusProperty const * property, IkarusErrorData * error_out);
/// \brief Sets the value of a property of an entity. /// \brief Sets the value of a property of an entity.

View file

@ -19,6 +19,13 @@ IKARUS_BEGIN_HEADER
/// \brief A generic object. Wraps all types of objects, including folders. /// \brief A generic object. Wraps all types of objects, including folders.
struct IkarusObject; struct IkarusObject;
/// \brief Fetches the project of an object.
/// \param object The object to fetch the project from.
/// \pre \li Must not be null.
/// \param error_out \see errors.h
/// \return The project of the object or null if an error occurs.
IKA_API struct IkarusProject * ikarus_object_get_project(IkarusObject const * object, IkarusErrorData * error_out);
/// \brief Compares two objects for equality. /// \brief Compares two objects for equality.
/// \details This neither compares the pointers nor does a deep copy. Instead it figures out if the objects _are_ the /// \details This neither compares the pointers nor does a deep copy. Instead it figures out if the objects _are_ the
/// same object. /// same object.
@ -35,9 +42,6 @@ IKA_API bool ikarus_object_is_equal(IkarusObject const * lhs, IkarusObject const
/// \param blueprint_visitor The function to call if the object is a blueprint. Skipped if null. /// \param blueprint_visitor The function to call if the object is a blueprint. Skipped if null.
/// \param property_visitor The function to call if the object is a property. Skipped if null. /// \param property_visitor The function to call if the object is a property. Skipped if null.
/// \param entity_visitor The function to call if the object is an entity. Skipped if null. /// \param entity_visitor The function to call if the object is an entity. Skipped if null.
/// \param blueprint_folder_visitor The function to call if the object is a blueprint folder. Skipped if null.
/// \param property_folder_visitor The function to call if the object is a property folder. Skipped if null.
/// \param entity_folder_visitor The function to call if the object is an entity folder. Skipped if null.
/// \param data The data passed to the visitor functions. /// \param data The data passed to the visitor functions.
/// \param error_out \see errors.h /// \param error_out \see errors.h
IKA_API void ikarus_object_visit( IKA_API void ikarus_object_visit(
@ -45,9 +49,6 @@ IKA_API void ikarus_object_visit(
void (*blueprint_visitor)(struct IkarusBlueprint *, void *), void (*blueprint_visitor)(struct IkarusBlueprint *, void *),
void (*property_visitor)(struct IkarusProperty *, void *), void (*property_visitor)(struct IkarusProperty *, void *),
void (*entity_visitor)(struct IkarusEntity *, void *), void (*entity_visitor)(struct IkarusEntity *, void *),
void (*blueprint_folder_visitor)(struct IkarusBlueprintFolder *, void *),
void (*property_folder_visitor)(struct IkarusPropertyFolder *, void *),
void (*entity_folder_visitor)(struct IkarusEntityFolder *, void *),
void * data, void * data,
IkarusErrorData * error_out IkarusErrorData * error_out
); );
@ -58,9 +59,6 @@ IKA_API void ikarus_object_visit_const(
void (*blueprint_visitor)(struct IkarusBlueprint const *, void *), void (*blueprint_visitor)(struct IkarusBlueprint const *, void *),
void (*property_visitor)(struct IkarusProperty const *, void *), void (*property_visitor)(struct IkarusProperty const *, void *),
void (*entity_visitor)(struct IkarusEntity const *, void *), void (*entity_visitor)(struct IkarusEntity const *, void *),
void (*blueprint_folder_visitor)(struct IkarusBlueprintFolder const *, void *),
void (*property_folder_visitor)(struct IkarusPropertyFolder const *, void *),
void (*entity_folder_visitor)(struct IkarusEntityFolder const *, void *),
void * data, void * data,
IkarusErrorData * error_out IkarusErrorData * error_out
); );

View file

@ -25,10 +25,9 @@ enum IkarusObjectType {
/// \brief Converts an IkarusObjectType to a string. /// \brief Converts an IkarusObjectType to a string.
/// \param type The type to convert. /// \param type The type to convert.
/// \param error_out \see errors.h
/// \return The string representation of the type. /// \return The string representation of the type.
/// \remark The returned string must not be freed. /// \remark The returned string must not be freed.
char const * ikarus_object_type_to_string(IkarusObjectType type, IkarusErrorData * error_out); char const * ikarus_object_type_to_string(IkarusObjectType type);
IKARUS_END_HEADER IKARUS_END_HEADER

View file

@ -46,7 +46,7 @@ ikarus_number_property_get_default_value(struct IkarusNumberProperty * property,
/// \param property The number property. /// \param property The number property.
/// \pre \li Must not be null. /// \pre \li Must not be null.
/// \pre \li Must exist. /// \pre \li Must exist.
/// \param default_value The default value. /// \param new_default_value The default value.
/// \pre \li Must not be null. /// \pre \li Must not be null.
/// \pre \li Must be a valid value for the property. /// \pre \li Must be a valid value for the property.
/// \param error_out \see errors.h /// \param error_out \see errors.h
@ -54,7 +54,7 @@ ikarus_number_property_get_default_value(struct IkarusNumberProperty * property,
/// default values and other settings. /// default values and other settings.
IKA_API void ikarus_number_property_set_default_value( IKA_API void ikarus_number_property_set_default_value(
struct IkarusNumberProperty * property, struct IkarusNumberProperty * property,
struct IkarusNumberValue * default_value, struct IkarusNumberValue * new_default_value,
IkarusErrorData * error_out IkarusErrorData * error_out
); );

View file

@ -89,7 +89,7 @@ IKA_API struct IkarusPropertySource const * ikarus_property_get_source(IkarusPro
/// \param error_out \see errors.h /// \param error_out \see errors.h
/// \return The default value of the property or null if an error occurs. /// \return The default value of the property or null if an error occurs.
/// \remark Must be freed using #ikarus_free. /// \remark Must be freed using #ikarus_free.
IKA_API struct IkarusValue const * ikarus_property_get_default_value(IkarusProperty const * property, IkarusErrorData * error_out); IKA_API struct IkarusValue * ikarus_property_get_default_value(IkarusProperty const * property, IkarusErrorData * error_out);
/// \brief Visits a property. Calling the appropriate function for the property's type. /// \brief Visits a property. Calling the appropriate function for the property's type.
/// \param property The property to visit. /// \param property The property to visit.

View file

@ -15,11 +15,11 @@ IKARUS_BEGIN_HEADER
/// available. /// available.
enum IkarusPropertyType { enum IkarusPropertyType {
/// \brief A true/false boolean-esque value. /// \brief A true/false boolean-esque value.
IkarusPropertyType_Toggle, IkarusPropertyType_Toggle = 0x10000000,
/// \brief A numeric value, limited to IEEE 80 bit floating point numbers. /// \brief A numeric value, limited to IEEE 80 bit floating point numbers.
IkarusPropertyType_Number, IkarusPropertyType_Number = 0x20000000,
/// \brief An arbitrary UTF-8 textual value. /// \brief An arbitrary UTF-8 textual value.
IkarusPropertyType_Text, IkarusPropertyType_Text = 0x30000000,
}; };
IKARUS_END_HEADER IKARUS_END_HEADER

View file

@ -45,7 +45,7 @@ IKA_API struct IkarusTextValue * ikarus_text_property_get_default_value(struct I
/// \param property The text property. /// \param property The text property.
/// \pre \li Must not be null. /// \pre \li Must not be null.
/// \pre \li Must exist. /// \pre \li Must exist.
/// \param default_value The default value. /// \param new_default_value The default value.
/// \pre \li Must not be null. /// \pre \li Must not be null.
/// \pre \li Must be a valid value for the property. /// \pre \li Must be a valid value for the property.
/// \param error_out \see errors.h /// \param error_out \see errors.h
@ -53,7 +53,7 @@ IKA_API struct IkarusTextValue * ikarus_text_property_get_default_value(struct I
/// default values and other settings. /// default values and other settings.
IKA_API void ikarus_text_property_set_default_value( IKA_API void ikarus_text_property_set_default_value(
struct IkarusTextProperty * property, struct IkarusTextProperty * property,
struct IkarusTextValue * default_value, struct IkarusTextValue * new_default_value,
IkarusErrorData * error_out IkarusErrorData * error_out
); );

View file

@ -46,7 +46,7 @@ ikarus_toggle_property_get_default_value(struct IkarusToggleProperty * property,
/// \param property The toggle property. /// \param property The toggle property.
/// \pre \li Must not be null. /// \pre \li Must not be null.
/// \pre \li Must exist. /// \pre \li Must exist.
/// \param default_value The default value. /// \param new_default_value The default value.
/// \pre \li Must not be null. /// \pre \li Must not be null.
/// \pre \li Must be a valid value for the property. /// \pre \li Must be a valid value for the property.
/// \param error_out \see errors.h /// \param error_out \see errors.h
@ -54,7 +54,7 @@ ikarus_toggle_property_get_default_value(struct IkarusToggleProperty * property,
/// default values and other settings. /// default values and other settings.
IKA_API void ikarus_toggle_property_set_default_value( IKA_API void ikarus_toggle_property_set_default_value(
struct IkarusToggleProperty * property, struct IkarusToggleProperty * property,
struct IkarusToggleValue * default_value, struct IkarusToggleValue * new_default_value,
IkarusErrorData * error_out IkarusErrorData * error_out
); );

View file

@ -51,39 +51,13 @@ IKA_API IkarusProject * ikarus_project_create_in_memory(char const * name, Ikaru
/// ikarus_project_delete /// ikarus_project_delete
IKA_API IkarusProject * ikarus_project_open(char const * path, IkarusErrorData * error_out); IKA_API IkarusProject * ikarus_project_open(char const * path, IkarusErrorData * error_out);
/// \brief Copies a project to a new location.
/// \details The new project is not opened.
/// \param project The project to copy.
/// \pre \li Must not be null.
/// \pre \li Must exist.
/// \param target_path The new location of the project.
/// \pre \li Must not be null.
/// \pre \li Must point to a valid unused path on the system.
/// \param target_name The name of the new project.
/// \pre \li Must not be null.
/// \pre \li Must not be empty.
/// \param error_out \see errors.h
/// \remark If successful the project connection remains intact. The previous location will still exist.
IKA_API void
ikarus_project_copy(IkarusProject const * project, char const * target_path, char const * target_name, IkarusErrorData * error_out);
/// \brief Deletes a project and all its associated data from the filesystem.
/// \param project The project to delete.
/// \pre \li Must not be null.
/// \pre \li Must exist.
/// \param error_out \see errors.h
/// \remark also frees the project.
/// \remark In-Memory projects will just be freed.
/// \remark If deletion fails, the project pointer remains intact.
IKA_API void ikarus_project_delete(IkarusProject * project, IkarusErrorData * error_out);
/// \brief Gets the name of a project. /// \brief Gets the name of a project.
/// \param project The project to get the name of. /// \param project The project to get the name of.
/// \pre \li Must not be null. /// \pre \li Must not be null.
/// \pre \li Must exist. /// \pre \li Must exist.
/// \param error_out \see errors.h /// \param error_out \see errors.h
/// \return The name of the project. /// \return The name of the project.
/// \remark Must be freed using #ikarus_free. /// \remark Ownership remains with libikarus, must not be freed.
IKA_API char const * ikarus_project_get_name(IkarusProject const * project, IkarusErrorData * error_out); IKA_API char const * ikarus_project_get_name(IkarusProject const * project, IkarusErrorData * error_out);
/// \brief Sets the name of a project. /// \brief Sets the name of a project.
@ -102,30 +76,9 @@ IKA_API void ikarus_project_set_name(IkarusProject * project, char const * new_n
/// \pre \li Must exist. /// \pre \li Must exist.
/// \param error_out \see errors.h /// \param error_out \see errors.h
/// \return The path of the project. /// \return The path of the project.
/// \remark Must be freed using #ikarus_free. /// \remark Ownership remains with libikarus, must not be freed.
IKA_API char const * ikarus_project_get_path(IkarusProject const * project, IkarusErrorData * error_out); IKA_API char const * ikarus_project_get_path(IkarusProject const * project, IkarusErrorData * error_out);
/// \brief Moves a project to a new location.
/// \param project The project to move.
/// \pre \li Must not be null.
/// \pre \li Must exist.
/// \param target_path The new location of the project.
/// \pre \li Must not be null.
/// \pre \li Must point to a valid unused path on the system.
/// \param error_out \see errors.h
/// \remark If successful the project connection remains intact. The previous location will not exist anymore.
/// \remark Due to the nature of filesystems this function may not be atomic.
IKA_API void ikarus_project_move(IkarusProject * project, char const * target_path, IkarusErrorData * error_out);
/// \brief Gets the blueprint root folder of a project.
/// \param project The project to get the blueprint root folder of.
/// \pre \li Must not be null.
/// \pre \li Must exist.
/// \param error_out \see errors.h
/// \return The blueprint root folder of the project or null if an error occurs.
/// \remark Must be freed using #ikarus_free.
IKA_API struct IkarusBlueprintFolder * ikarus_project_get_blueprint_root_folder(IkarusProject const * project, IkarusErrorData * error_out);
/// \brief Gets the blueprints of a project. /// \brief Gets the blueprints of a project.
/// \param project The project to get the blueprints of. /// \param project The project to get the blueprints of.
/// \pre \li Must not be null. /// \pre \li Must not be null.
@ -135,7 +88,7 @@ IKA_API struct IkarusBlueprintFolder * ikarus_project_get_blueprint_root_folder(
/// \param blueprints_out_size The size of the buffer. /// \param blueprints_out_size The size of the buffer.
/// \param error_out \see errors.h /// \param error_out \see errors.h
IKA_API void ikarus_project_get_blueprints( IKA_API void ikarus_project_get_blueprints(
IkarusProject const * project, IkarusProject * project,
struct IkarusBlueprint ** blueprints_out, struct IkarusBlueprint ** blueprints_out,
size_t blueprints_out_size, size_t blueprints_out_size,
IkarusErrorData * error_out IkarusErrorData * error_out
@ -147,16 +100,7 @@ IKA_API void ikarus_project_get_blueprints(
/// \pre \li Must exist. /// \pre \li Must exist.
/// \param error_out \see errors.h /// \param error_out \see errors.h
/// \return The number of blueprints or undefined if an error occurs. /// \return The number of blueprints or undefined if an error occurs.
IKA_API size_t ikarus_project_get_blueprint_count(IkarusProject const * project, IkarusErrorData * error_out); IKA_API size_t ikarus_project_get_blueprint_count(IkarusProject * project, IkarusErrorData * error_out);
/// \brief Gets the entity root folder of a project.
/// \param project The project to get the entity root folder of.
/// \pre \li Must not be null.
/// \pre \li Must exist.
/// \param error_out \see errors.h
/// \return The entity root folder of the project or null if an error occurs.
/// \remark Must be freed using #ikarus_free.
IKA_API struct IkarusEntityFolder * ikarus_project_get_entity_root_folder(IkarusProject const * project, IkarusErrorData * error_out);
/// \brief Gets the entities of a project. /// \brief Gets the entities of a project.
/// \param project The project to get the entities of. /// \param project The project to get the entities of.
@ -167,7 +111,7 @@ IKA_API struct IkarusEntityFolder * ikarus_project_get_entity_root_folder(Ikarus
/// \param entities_out_size The size of the buffer. /// \param entities_out_size The size of the buffer.
/// \param error_out \see errors.h /// \param error_out \see errors.h
IKA_API void ikarus_project_get_entities( IKA_API void ikarus_project_get_entities(
IkarusProject const * project, IkarusProject * project,
struct IkarusEntity ** entities_out, struct IkarusEntity ** entities_out,
size_t entities_out_size, size_t entities_out_size,
IkarusErrorData * error_out IkarusErrorData * error_out
@ -179,7 +123,7 @@ IKA_API void ikarus_project_get_entities(
/// \pre \li Must exist. /// \pre \li Must exist.
/// \param error_out \see errors.h /// \param error_out \see errors.h
/// \return The number of entities or undefined if an error occurs. /// \return The number of entities or undefined if an error occurs.
IKA_API size_t ikarus_project_get_entity_count(IkarusProject const * project, IkarusErrorData * error_out); IKA_API size_t ikarus_project_get_entity_count(IkarusProject * project, IkarusErrorData * error_out);
IKARUS_END_HEADER IKARUS_END_HEADER

View file

@ -0,0 +1,46 @@
#pragma once
/// \file entity_property_value.h
/// \author Folling <folling@ikarus.world>
/// \defgroup entity_property_values EntityPropertyValue
/// \brief Values in relation to an entity and one of its properties.
/// @{
#include <ikarus/errors.h>
#include <ikarus/macros.h>
#include <ikarus/values/entity_property_value.h>
IKARUS_BEGIN_HEADER
/// \brief Like an \ref value.h "IkarusValue", but in relation to an entity and one of its properties
struct IkarusEntityPropertyValue;
/// \brief Fetches the entity of an entity property value.
/// \param value The entity property value.
/// \pre \li Must not be null.
/// \param error_out \see errors.h
/// \return The entity of the entity property value.
/// \remark This value is owned by the entity property value and must not be freed directly.
struct IkarusEntity const * ikarus_entity_property_value_get_entity(IkarusEntityPropertyValue const * value, IkarusErrorData * error_out);
/// \brief Fetches the property of an entity property value.
/// \param value The entity property value.
/// \pre \li Must not be null.
/// \param error_out \see errors.h
/// \return The property of the entity property value.
/// \remark This value is owned by the entity property value and must not be freed directly.
struct IkarusProperty const *
ikarus_entity_property_value_get_property(IkarusEntityPropertyValue const * value, IkarusErrorData * error_out);
/// \brief Fetches the value of an entity property value.
/// \param value The entity property value.
/// \pre \li Must not be null.
/// \param error_out \see errors.h
/// \return The value of the entity property value.
/// \remark This value is owned by the entity property value and must not be freed directly.
struct IkarusValue const * ikarus_entity_property_value_get_value(IkarusEntityPropertyValue const * value, IkarusErrorData * error_out);
IKARUS_END_HEADER
/// @}

View file

@ -3,9 +3,6 @@
/// \file value.h /// \file value.h
/// \author Folling <folling@ikarus.world> /// \author Folling <folling@ikarus.world>
#include <ikarus/errors.h>
#include <ikarus/macros.h>
/// \defgroup values Values /// \defgroup values Values
/// \brief The values of properties. /// \brief The values of properties.
/// \details Each entity has a value for each property it is associated with. /// \details Each entity has a value for each property it is associated with.
@ -17,6 +14,9 @@
/// property's settings. \see PropertyType /// property's settings. \see PropertyType
/// @{ /// @{
#include <ikarus/errors.h>
#include <ikarus/macros.h>
IKARUS_BEGIN_HEADER IKARUS_BEGIN_HEADER
/// \brief The common type for all values. /// \brief The common type for all values.

View file

@ -3,6 +3,8 @@ file(
FILES FILES
"*.hpp" "*.hpp"
"*.cpp" "*.cpp"
"*.h"
"*.c"
) )
set(SOURCE_FILES ${FILES} PARENT_SCOPE) set(SOURCE_FILES ${FILES} PARENT_SCOPE)

View file

@ -11,61 +11,39 @@
char const * get_error_info_name(IkarusErrorInfo info) { char const * get_error_info_name(IkarusErrorInfo info) {
switch (info) { switch (info) {
case IkarusErrorInfo_None: return "None"; case IkarusErrorInfo_None: return "None";
case IkarusErrorInfo_Client: return "Client";
case IkarusErrorInfo_Dependency: return "Dependency";
case IkarusErrorInfo_Filesystem: return "Filesystem";
case IkarusErrorInfo_Database: return "Database";
case IkarusErrorInfo_OS: return "OS";
case IkarusErrorInfo_LibIkarus: return "libikarus";
case IkarusErrorInfo_Client_Misuse: return "Misuse"; case IkarusErrorInfo_Client_Misuse: return "Client::Misuse";
case IkarusErrorInfo_Client_InvalidInput: return "InvalidInput"; case IkarusErrorInfo_Client_InvalidInput: return "Client::InvalidInput";
case IkarusErrorInfo_Client_InvalidFormat: return "InvalidFormat"; case IkarusErrorInfo_Client_InvalidFormat: return "Client::InvalidFormat";
case IkarusErrorInfo_Client_ConstraintViolated: return "ConstraintViolated"; case IkarusErrorInfo_Client_ConstraintViolated: return "Client::ConstraintViolated";
case IkarusErrorInfo_Filesystem_NotFound: return "NotFound"; case IkarusErrorInfo_Filesystem_NotFound: return "Filesystem::NotFound";
case IkarusErrorInfo_Filesystem_AlreadyExists: return "AlreadyExists"; case IkarusErrorInfo_Filesystem_AlreadyExists: return "Filesystem::AlreadyExists";
case IkarusErrorInfo_Filesystem_MissingPermissions: return "MissingPermissions"; case IkarusErrorInfo_Filesystem_MissingPermissions: return "Filesystem::MissingPermissions";
case IkarusErrorInfo_Filesystem_InsufficientSpace: return "InsufficientSpace"; case IkarusErrorInfo_Filesystem_InsufficientSpace: return "Filesystem::InsufficientSpace";
case IkarusErrorInfo_Filesystem_InvalidPath: return "InvalidPath"; case IkarusErrorInfo_Filesystem_InvalidPath: return "Filesystem::InvalidPath";
case IkarusErrorInfo_Database_ConnectionFailed: return "ConnectionFailed"; case IkarusErrorInfo_Database_ConnectionFailed: return "Database::ConnectionFailed";
case IkarusErrorInfo_Database_QueryFailed: return "QueryFailed"; case IkarusErrorInfo_Database_QueryFailed: return "Database::QueryFailed";
case IkarusErrorInfo_Database_MigrationFailed: return "MigrationFailed"; case IkarusErrorInfo_Database_MigrationFailed: return "Database::MigrationFailed";
case IkarusErrorInfo_Database_InvalidState: return "InvalidState"; case IkarusErrorInfo_Database_InvalidState: return "Database::InvalidState";
case IkarusErrorInfo_OS_SystemCallFailed: return "SystemCallFailed"; case IkarusErrorInfo_OS_SystemCallFailed: return "OS::SystemCallFailed";
case IkarusErrorInfo_OS_InvalidReturnValue: return "InvalidReturnValue"; case IkarusErrorInfo_OS_InvalidReturnValue: return "OS::InvalidReturnValue";
case IkarusErrorInfo_OS_InsufficientMemory: return "InsufficientMemory"; case IkarusErrorInfo_OS_InsufficientMemory: return "OS::InsufficientMemory";
case IkarusErrorInfo_LibIkarus_InvalidState: return "InvalidState"; case IkarusErrorInfo_LibIkarus_InvalidState: return "LibIkarus::InvalidState";
case IkarusErrorInfo_LibIkarus_Timeout: return "Timeout"; case IkarusErrorInfo_LibIkarus_CannotPerformOperation: return "LibIkarus::CannotPerformOperation";
case IkarusErrorInfo_LibIkarus_Timeout: return "LibIkarus::Timeout";
default: return "Invalid"; default: return "Invalid";
} }
} }
bool ikarus_error_data_is_success(IkarusErrorData const * data) { bool ikarus_error_data_is_success(IkarusErrorData const * data) {
return data->infos[0] == IkarusErrorInfo_None; return data->info == IkarusErrorInfo_None;
} }
bool ikarus_error_data_is_error(IkarusErrorData const * data) { bool ikarus_error_data_is_error(IkarusErrorData const * data) {
return data->infos[0] != IkarusErrorInfo_None; return data->info != IkarusErrorInfo_None;
}
char const * ikarus_error_data_pretty_format(IkarusErrorData const * data) {
if (ikarus_error_data_is_success(data)) {
return "Success";
}
auto const formatted = fmt::format(
"{} - {}",
fmt::join(
data->infos | std::views::take_while(cppbase::pred_ne(IkarusErrorInfo_None)) | std::views::transform(get_error_info_name),
"->"
),
data->message
);
return strndup(formatted.data(), formatted.size());
} }

93
src/errors.hpp Normal file
View file

@ -0,0 +1,93 @@
#pragma once
#include <string_view>
#include <ikarus/errors.h>
inline void safe_strcpy(char * dest, std::string_view src, size_t dest_size) {
for (int i = 0; i < dest_size; ++i) {
if (src[i] == '\0') {
dest[i] = '\0';
return;
}
dest[i] = src[i];
}
}
#define IKARUS_SET_ERROR(msg, err_info) \
if (error_out != nullptr) { \
safe_strcpy(static_cast<char *>(error_out->message), msg, IKARUS_ERROR_DATA_MAX_MESSAGE_LIMIT); \
error_out->info = err_info; \
}
#define IKARUS_FAIL(ret, msg, err_info) \
IKARUS_SET_ERROR(msg, err_info); \
return ret
#define IKARUS_FAIL_IF(condition, ret, msg, err_info) \
if (condition) { \
IKARUS_SET_ERROR(msg, err_info) \
return ret; \
}
#define IKARUS_FAIL_IF_ERROR(ret) \
if (ikarus_error_data_is_error(error_out)) { \
return ret; \
}
#define IKARUS_FAIL_IF_NULL(ptr, ret) IKARUS_FAIL_IF(((ptr) == nullptr), ret, #ptr " must not be null", IkarusErrorInfo_Client_InvalidNull)
#define IKARUS_TRY_OR_FAIL_IMPL(var_name, msg, err_info, ...) \
auto var_name = __VA_ARGS__; \
if (var_name.is_error()) { \
IKARUS_SET_ERROR(fmt::format(msg, var_name.unwrap_error()), err_info); \
return var_name; \
}
#define IKARUS_TRY_OR_FAIL(msg, err_info, ...) IKARUS_TRY_OR_FAIL_IMPL(CPPBASE_UNIQUE_NAME(result), msg, err_info, __VA_ARGS__);
#define IKARUS_TRYRV_OR_FAIL_IMPL(var_name, ret, msg, err_info, ...) \
auto var_name = __VA_ARGS__; \
if (var_name.is_error()) { \
IKARUS_SET_ERROR(fmt::format(msg, var_name.unwrap_error()), err_info); \
return ret; \
}
#define IKARUS_TRYRV_OR_FAIL(ret, msg, err_info, ...) \
IKARUS_TRYRV_OR_FAIL_IMPL(CPPBASE_UNIQUE_NAME(result), ret, msg, err_info, __VA_ARGS__);
#define IKARUS_VTRY_OR_FAIL_IMPL(var_name, value, msg, err_info, ...) \
auto var_name = __VA_ARGS__; \
if (var_name.is_error()) { \
IKARUS_SET_ERROR(fmt::format(msg, var_name.unwrap_error()), err_info); \
return var_name; \
} \
value = var_name.unwrap_value()
#define IKARUS_VTRY_OR_FAIL(value, msg, err_info, ...) \
IKARUS_VTRY_OR_FAIL_IMPL(CPPBASE_UNIQUE_NAME(result), value, msg, err_info, __VA_ARGS__);
#define IKARUS_VTRYRV_OR_FAIL_IMPL(var_name, value, ret, msg, err_info, ...) \
auto var_name = __VA_ARGS__; \
if (var_name.is_error()) { \
IKARUS_SET_ERROR(fmt::format(msg, var_name.unwrap_error()), err_info); \
return ret; \
} \
value = var_name.unwrap_value()
#define IKARUS_VTRYRV_OR_FAIL(value, ret, msg, err_info, ...) \
IKARUS_VTRYRV_OR_FAIL_IMPL(CPPBASE_UNIQUE_NAME(result), value, ret, msg, err_info, __VA_ARGS__);
#define IKARUS_FAIL_IF_OBJECT_MISSING_IMPL(var_name, obj, ret) \
IKARUS_VTRYRV_OR_FAIL( \
bool const var_name, \
ret, \
"unable to check whether object exists: {}", \
IkarusErrorInfo_Database_QueryFailed, \
(obj)->project->db->template query_one<bool>("SELECT EXISTS(SELECT 1 FROM `objects` WHERE `id` = ?)", (obj)->id) \
) \
\
IKARUS_FAIL_IF(!(var_name), ret, "object does not exist", IkarusErrorInfo_Client_Misuse);
#define IKARUS_FAIL_IF_OBJECT_MISSING(obj, ret) IKARUS_FAIL_IF_OBJECT_MISSING_IMPL(CPPBASE_UNIQUE_NAME(exists), obj, ret);

View file

@ -1,11 +1,13 @@
#include "ikarus/objects/blueprint.h" #include "ikarus/objects/blueprint.h"
#include "ikarus/objects/properties/property.h"
#include "objects/blueprint.hpp" #include "objects/blueprint.hpp"
#include <cppbase/logger.hpp> #include <cppbase/logger.hpp>
#include <cppbase/result.hpp> #include <cppbase/result.hpp>
#include <cppbase/strings.hpp> #include <cppbase/strings.hpp>
#include <errors.hpp>
#include <objects/entity.hpp> #include <objects/entity.hpp>
#include <objects/properties/property.hpp> #include <objects/properties/property.hpp>
#include <persistence/project.hpp> #include <persistence/project.hpp>
@ -13,117 +15,149 @@
IkarusBlueprint::IkarusBlueprint(IkarusProject * project, IkarusId id): IkarusBlueprint::IkarusBlueprint(IkarusProject * project, IkarusId id):
IkarusObject{project, id} {} IkarusObject{project, id} {}
IkarusBlueprint * ikarus_blueprint_create(struct IkarusProject * project, char const * name) { IkarusBlueprint * ikarus_blueprint_create(struct IkarusProject * project, char const * name, IkarusErrorData * error_out) {
if (cppbase::is_empty_or_blank(name)) { IKARUS_FAIL_IF_NULL(project, nullptr);
project->set_error("blueprint name must not be empty", true, IkarusErrorInfo_Source_Client, IkarusErrorInfo_Type_Client_Input); IKARUS_FAIL_IF_NULL(name, nullptr);
IKARUS_FAIL_IF(cppbase::is_empty_or_blank(name), nullptr, "name must not be empty", IkarusErrorInfo_Client_InvalidInput);
return nullptr; IKARUS_VTRYRV_OR_FAIL(
} IkarusId const id,
nullptr,
project->db "failed to create blueprint: {}",
->transact([name](auto * db) { IkarusErrorInfo_Database_QueryFailed,
project->db->transact([name](auto * db) -> cppbase::Result<IkarusId, sqlitecpp::TransactionError> {
TRY(db->execute("INSERT INTO `objects`(`type`, `name`) VALUES(?, ?, ?)", IkarusObjectType_Blueprint, name)); TRY(db->execute("INSERT INTO `objects`(`type`, `name`) VALUES(?, ?, ?)", IkarusObjectType_Blueprint, name));
auto id = ikarus_id_from_data_and_type(db->last_insert_rowid(), IkarusObjectType_Blueprint); auto id = ikarus_id_from_data_and_type(db->last_insert_rowid(), IkarusObjectType_Blueprint);
TRY(db->execute("INSERT INTO `blueprints`(`id`) VALUES(?)", id)); TRY(db->execute("INSERT INTO `blueprints`(`id`) VALUES(?)", id));
return cppbase::ok(); return cppbase::ok(id);
}) })
.on_error([project](auto const & err) {
project->set_error(
fmt::format("failed to create blueprint: {}", err),
true,
IkarusErrorInfo_Source_SubSystem,
IkarusErrorInfo_Type_SubSystem_Database
); );
});
return project->get_blueprint(id);
} }
void ikarus_blueprint_delete(IkarusBlueprint * blueprint) { void ikarus_blueprint_delete(IkarusBlueprint * blueprint, IkarusErrorData * error_out) {
blueprint->project->db->execute("DELETE FROM `objects` WHERE `id` = ?", blueprint->id).on_error([blueprint](auto const & err) { IKARUS_FAIL_IF_NULL(blueprint, );
blueprint->project->set_error( IKARUS_FAIL_IF_OBJECT_MISSING(blueprint, );
fmt::format("failed to delete blueprint from objects table: {}", err),
true, IKARUS_TRYRV_OR_FAIL(
IkarusErrorInfo_Source_SubSystem, ,
IkarusErrorInfo_Type_SubSystem_Database "unable to delete blueprint: {}",
IkarusErrorInfo_Database_QueryFailed,
blueprint->project->db->execute("DELETE FROM `objects` WHERE `id` = ?", blueprint->id)
); );
});
blueprint->project->uncache(blueprint);
} }
void ikarus_blueprint_get_properties( void ikarus_blueprint_get_properties(
IkarusBlueprint const * blueprint, IkarusBlueprint const * blueprint,
struct IkarusProperty ** properties_out, struct IkarusProperty ** properties_out,
size_t properties_out_size size_t properties_out_size,
IkarusErrorData * error_out
) { ) {
IkarusId ids[properties_out_size]; IKARUS_FAIL_IF_NULL(blueprint, );
IKARUS_FAIL_IF_OBJECT_MISSING(blueprint, );
IKARUS_FAIL_IF_NULL(properties_out, );
TRYRV( if (properties_out_size == 0) {
return;
}
std::tuple<IkarusId, IkarusPropertyType> ids_and_types[properties_out_size];
IKARUS_TRYRV_OR_FAIL(
, ,
blueprint->project->db "unable to fetch blueprint properties from database: {}",
->query_many_buffered<IkarusId>("SELECT `id` FROM `properties` WHERE `source` = ?", ids, properties_out_size, blueprint->id) IkarusErrorInfo_Database_QueryFailed,
.on_error([&](auto const & err) { blueprint->project->db->query_many_buffered<IkarusId, IkarusPropertyType>(
blueprint->project->set_error( "SELECT `id` FROM `properties` WHERE `source` = ?",
fmt::format("failed to fetch blueprint properties from database: {}", err), ids_and_types,
true, properties_out_size,
IkarusErrorInfo_Source_SubSystem, blueprint->id
IkarusErrorInfo_Type_SubSystem_Database )
); )
})
);
// not atomic, could be switched to two loops if necessary
for (size_t i = 0; i < properties_out_size; ++i) { for (size_t i = 0; i < properties_out_size; ++i) {
IkarusId id = ids[i]; auto [id, type] = ids_and_types[i];
VTRYRV(auto const type, , IkarusProperty::get_property_type(blueprint->project, id));
properties_out[i] = blueprint->project->get_property(id, type); properties_out[i] = blueprint->project->get_property(id, type);
} }
} }
size_t ikarus_blueprint_get_property_count(IkarusBlueprint const * blueprint) { size_t ikarus_blueprint_get_property_count(IkarusBlueprint const * blueprint, IkarusErrorData * error_out) {
return blueprint->project->db->query_one<int64_t>("SELECT COUNT(*) FROM `properties` WHERE `source` = ?", blueprint->id) IKARUS_FAIL_IF_NULL(blueprint, 0);
.unwrap_value_or(0); IKARUS_FAIL_IF_OBJECT_MISSING(blueprint, 0);
IKARUS_VTRYRV_OR_FAIL(
auto const ret,
0,
"unable to fetch blueprint property count from database: {}",
IkarusErrorInfo_Database_QueryFailed,
blueprint->project->db->query_one<int64_t>("SELECT COUNT(*) FROM `properties` WHERE `source` = ?", blueprint->id)
);
return ret;
} }
void ikarus_blueprint_get_linked_entities( void ikarus_blueprint_get_linked_entities(
IkarusBlueprint const * blueprint, IkarusBlueprint const * blueprint,
struct IkarusEntity ** entities_out, struct IkarusEntity ** entities_out,
size_t entities_out_size size_t entities_out_size,
IkarusErrorData * error_out
) { ) {
IKARUS_FAIL_IF_NULL(blueprint, );
IKARUS_FAIL_IF_OBJECT_MISSING(blueprint, );
IKARUS_FAIL_IF_NULL(entities_out, );
if (entities_out_size == 0) {
return;
}
IkarusId ids[entities_out_size]; IkarusId ids[entities_out_size];
TRYRV( IKARUS_TRYRV_OR_FAIL(
, ,
blueprint->project->db "unable to fetch blueprint linked entities from database: {}",
->query_many_buffered<IkarusId>( IkarusErrorInfo_Database_QueryFailed,
blueprint->project->db->query_many_buffered<IkarusId>(
"SELECT `entity` FROM `entity_blueprint_links` WHERE `blueprint` = ?", "SELECT `entity` FROM `entity_blueprint_links` WHERE `blueprint` = ?",
ids, ids,
entities_out_size, entities_out_size,
blueprint->id blueprint->id
) )
.on_error([&](auto const & err) { )
blueprint->project->set_error(
fmt::format("failed to fetch linked entities from database: {}", err),
true,
IkarusErrorInfo_Source_SubSystem,
IkarusErrorInfo_Type_SubSystem_Database
);
})
);
for (size_t i = 0; i < entities_out_size; ++i) { for (size_t i = 0; i < entities_out_size; ++i) {
entities_out[i] = blueprint->project->get_entity(ids[i]); entities_out[i] = blueprint->project->get_entity(ids[i]);
} }
} }
size_t ikarus_blueprint_get_linked_entity_count(IkarusBlueprint const * blueprint) { size_t ikarus_blueprint_get_linked_entity_count(IkarusBlueprint const * blueprint, IkarusErrorData * error_out) {
return blueprint->project->db->query_one<int64_t>("SELECT COUNT(*) FROM `entity_blueprint_links` WHERE `blueprint` = ?", blueprint->id) IKARUS_FAIL_IF_NULL(blueprint, 0);
.unwrap_value_or(0); IKARUS_FAIL_IF_OBJECT_MISSING(blueprint, 0);
IKARUS_VTRYRV_OR_FAIL(
auto const ret,
0,
"unable to fetch blueprint linked entity count from database: {}",
IkarusErrorInfo_Database_QueryFailed,
blueprint->project->db->query_one<int64_t>("SELECT COUNT(*) FROM `entity_blueprint_links` WHERE `blueprint` = ?", blueprint->id)
);
return ret;
} }
IkarusObject * ikarus_blueprint_to_object(IkarusBlueprint * blueprint) { // No existence checks here for performance reasons. All methods on IkarusObject perform this check anyway.
IkarusObject * ikarus_blueprint_to_object(IkarusBlueprint * blueprint, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(blueprint, nullptr);
return static_cast<IkarusObject *>(blueprint); return static_cast<IkarusObject *>(blueprint);
} }
IkarusObject const * ikarus_blueprint_to_object_const(IkarusBlueprint const * blueprint) { IkarusObject const * ikarus_blueprint_to_object_const(IkarusBlueprint const * blueprint, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(blueprint, nullptr);
return static_cast<IkarusObject const *>(blueprint); return static_cast<IkarusObject const *>(blueprint);
} }

View file

@ -1,36 +1,289 @@
#include "entity.hpp" #include "entity.hpp"
#include "values/value.hpp"
#include <cppbase/strings.hpp> #include <cppbase/strings.hpp>
#include <errors.hpp>
#include <objects/blueprint.hpp> #include <objects/blueprint.hpp>
#include <objects/util.hpp> #include <objects/properties/property.hpp>
#include <persistence/project.hpp> #include <persistence/project.hpp>
#include <values/entity_property_value.hpp>
IkarusEntity * ikarus_entity_create(struct IkarusProject * project, char const * name) { IkarusEntity * ikarus_entity_create(struct IkarusProject * project, char const * name, IkarusErrorData * error_out) {
return ikarus::util::insert_object( IKARUS_FAIL_IF_NULL(project, nullptr);
project, IKARUS_FAIL_IF_NULL(name, nullptr);
IkarusObjectType_Entity, IKARUS_FAIL_IF(cppbase::is_empty_or_blank(name), nullptr, "name must not be empty", IkarusErrorInfo_Client_InvalidInput);
name,
[](auto * db, IkarusId id) { return db->execute("INSERT INTO `entities`(`id`) VALUES(?)", id); }, IKARUS_VTRYRV_OR_FAIL(
[project](IkarusId id) { return project->get_entity(id); } IkarusId const id,
).unwrap_value_or(nullptr); nullptr,
"failed to create entity: {}",
IkarusErrorInfo_Database_QueryFailed,
project->db->transact([name](auto * db) -> cppbase::Result<IkarusId, sqlitecpp::TransactionError> {
TRY(db->execute("INSERT INTO `objects`(`type`, `name`) VALUES(?, ?, ?)", IkarusObjectType_Entity, name));
auto id = ikarus_id_from_data_and_type(db->last_insert_rowid(), IkarusObjectType_Entity);
TRY(db->execute("INSERT INTO `entities`(`id`) VALUES(?)", id));
return cppbase::ok(id);
})
);
return project->get_entity(id);
} }
void ikarus_entity_delete(IkarusEntity * entity) { void ikarus_entity_delete(IkarusEntity * entity, IkarusErrorData * error_out) {
ikarus::util::delete_object(entity); IKARUS_FAIL_IF_NULL(entity, );
IKARUS_FAIL_IF_OBJECT_MISSING(entity, );
IKARUS_TRYRV_OR_FAIL(
,
"unable to delete entity: {}",
IkarusErrorInfo_Database_QueryFailed,
entity->project->db->execute("DELETE FROM `objects` WHERE `id` == ?", entity->id)
);
entity->project->uncache(entity);
} }
bool ikarus_entity_is_linked_to_blueprint(IkarusEntity const * entity, struct IkarusBlueprint const * blueprint) { bool ikarus_entity_is_linked_to_blueprint(
return ikarus::util::check_exists( IkarusEntity const * entity,
entity, struct IkarusBlueprint const * blueprint,
ikarus::util::ExistsQueryData<IkarusId>{ IkarusErrorData * error_out
.table_name = "entity_blueprint_links", ) {
.where_field_name = "blueprint", IKARUS_FAIL_IF_NULL(entity, false);
.where_field_value = blueprint->id, IKARUS_FAIL_IF_OBJECT_MISSING(entity, false);
.relation_desc = "linked blueprints" IKARUS_FAIL_IF_NULL(blueprint, false);
} IKARUS_FAIL_IF_OBJECT_MISSING(blueprint, false);
IKARUS_VTRYRV_OR_FAIL(
auto const ret,
false,
"unable to check whether entity is linked to blueprint",
IkarusErrorInfo_Database_QueryFailed,
entity->project->db->query_one<bool>(
"SELECT EXISTS(SELECT 1 FROM `entity_blueprint_links` WHERE `entity` = ? AND `blueprint` = ?)",
entity->id,
blueprint->id
) )
.unwrap_value_or(false); )
return ret;
} }
bool ikarus_entity_link_to_blueprint(IkarusEntity * entity, struct IkarusBlueprint * blueprint) {} void ikarus_entity_link_to_blueprint(IkarusEntity * entity, struct IkarusBlueprint * blueprint, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(entity, );
IKARUS_FAIL_IF_OBJECT_MISSING(entity, );
IKARUS_FAIL_IF_NULL(blueprint, );
IKARUS_FAIL_IF_OBJECT_MISSING(blueprint, );
IKARUS_TRYRV_OR_FAIL(
,
"unable to link entity to blueprint: {}",
IkarusErrorInfo_Database_QueryFailed,
entity->project->db->execute(
"INSERT INTO `entity_blueprint_links`(`entity`, `blueprint`) VALUES(?, ?) ON CONFLICT(`entity`, `blueprint`) DO NOTHING",
entity->id,
blueprint->id
)
);
}
void ikarus_entity_unlink_from_blueprint(IkarusEntity * entity, struct IkarusBlueprint * blueprint, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(entity, );
IKARUS_FAIL_IF_OBJECT_MISSING(entity, );
IKARUS_FAIL_IF_NULL(blueprint, );
IKARUS_FAIL_IF_OBJECT_MISSING(blueprint, );
IKARUS_TRYRV_OR_FAIL(
,
"unable to unlink entity from blueprint: {}",
IkarusErrorInfo_Database_QueryFailed,
entity->project->db
->execute("DELETE FROM `entity_blueprint_links` WHERE `entity` = ? AND `blueprint` = ?", entity->id, blueprint->id)
);
}
void ikarus_entity_get_linked_blueprints(
IkarusEntity const * entity,
struct IkarusBlueprint ** blueprints_out,
size_t blueprints_out_size,
IkarusErrorData * error_out
) {
IKARUS_FAIL_IF_NULL(entity, );
IKARUS_FAIL_IF_OBJECT_MISSING(entity, );
IKARUS_FAIL_IF_NULL(blueprints_out, );
if (blueprints_out_size == 0) {
return;
}
IkarusId ids[blueprints_out_size];
IKARUS_TRYRV_OR_FAIL(
,
"unable to fetch entity linked blueprints from database: {}",
IkarusErrorInfo_Database_QueryFailed,
entity->project->db->query_many_buffered<IkarusId>(
"SELECT `blueprint` FROM `entity_blueprint_links` WHERE `entity` = ?",
ids,
blueprints_out_size,
entity->id
)
)
for (size_t i = 0; i < blueprints_out_size; ++i) {
blueprints_out[i] = entity->project->get_blueprint(ids[i]);
}
}
size_t ikarus_entity_get_linked_blueprint_count(IkarusEntity const * entity, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(entity, 0);
IKARUS_FAIL_IF_OBJECT_MISSING(entity, 0);
IKARUS_VTRYRV_OR_FAIL(
auto const ret,
0,
"unable to fetch entity linked blueprint count from database: {}",
IkarusErrorInfo_Database_QueryFailed,
entity->project->db->query_one<int64_t>("SELECT COUNT(*) FROM `entity_blueprint_links` WHERE `entity` = ?", entity->id)
);
return ret;
}
bool ikarus_entity_has_property(IkarusEntity const * entity, struct IkarusProperty const * property, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(entity, false);
IKARUS_FAIL_IF_OBJECT_MISSING(entity, false);
IKARUS_FAIL_IF_NULL(property, false);
IKARUS_FAIL_IF_OBJECT_MISSING(property, false);
IKARUS_VTRYRV_OR_FAIL(
auto const ret,
false,
"unable to check whether entity has property: {}",
IkarusErrorInfo_Database_QueryFailed,
entity->project->db->query_one<bool>(
"SELECT EXISTS(SELECT 1 FROM `entity_properties` WHERE `entity` = ? AND `property` = ?)",
entity->id,
property->id
)
)
return ret;
}
void ikarus_entity_get_properties(
IkarusEntity const * entity,
struct IkarusProperty ** properties_out,
size_t properties_out_size,
IkarusErrorData * error_out
) {
IKARUS_FAIL_IF_NULL(entity, );
IKARUS_FAIL_IF_OBJECT_MISSING(entity, );
IKARUS_FAIL_IF_NULL(properties_out, );
if (properties_out_size == 0) {
return;
}
std::tuple<IkarusId, IkarusPropertyType> ids_and_types[properties_out_size];
IKARUS_TRYRV_OR_FAIL(
,
"unable to fetch entity properties from database: {}",
IkarusErrorInfo_Database_QueryFailed,
entity->project->db->query_many_buffered<IkarusId, IkarusPropertyType>(
"SELECT `property`, `type` FROM `properties` WHERE `source` = ?",
ids_and_types,
properties_out_size,
entity->id
)
)
for (size_t i = 0; i < properties_out_size; ++i) {
auto [id, type] = ids_and_types[i];
properties_out[i] = entity->project->get_property(id, type);
}
}
size_t ikarus_entity_get_property_count(IkarusEntity const * entity, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(entity, 0);
IKARUS_FAIL_IF_OBJECT_MISSING(entity, 0);
IKARUS_VTRYRV_OR_FAIL(
size_t const ret,
0,
"unable to fetch entity property count from database: {}",
IkarusErrorInfo_Database_QueryFailed,
entity->project->db->query_one<int64_t>("SELECT COUNT(*) FROM `properties` WHERE `source` = ?", entity->id)
);
return ret;
}
struct IkarusEntityPropertyValue *
ikarus_entity_get_value(IkarusEntity const * entity, struct IkarusProperty const * property, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(entity, nullptr);
IKARUS_FAIL_IF_OBJECT_MISSING(entity, nullptr);
IKARUS_FAIL_IF_NULL(property, nullptr);
IKARUS_FAIL_IF_OBJECT_MISSING(property, nullptr);
auto * value = fetch_value_from_db(
entity->project,
error_out,
"SELECT IFNULL((SELECT `value` FROM `values` WHERE `entity` = ? AND `property` = ?), (SELECT `default_value` FROM `properties` WHERE `id` = ?))",
entity->id,
property->id,
property->id
);
IKARUS_FAIL_IF_ERROR(nullptr);
return new IkarusEntityPropertyValue{
.entity = entity,
.property = property,
.value = value,
};
}
void ikarus_entity_set_value(
IkarusEntity * entity,
struct IkarusProperty const * property,
struct IkarusEntityPropertyValue * value,
IkarusErrorData * error_out
) {
IKARUS_FAIL_IF_NULL(entity, );
IKARUS_FAIL_IF_OBJECT_MISSING(entity, );
IKARUS_FAIL_IF_NULL(property, );
IKARUS_FAIL_IF_OBJECT_MISSING(property, );
IKARUS_FAIL_IF_NULL(value, );
auto value_json_str = boost::json::serialize(value->value->to_json());
IKARUS_TRYRV_OR_FAIL(
,
"unable to set entity property value: {}",
IkarusErrorInfo_Database_QueryFailed,
entity->project->db->execute(
"INSERT INTO `values`(`entity`, `property`, `value`) VALUES(?, ?, ?) ON CONFLICT(`entity`, `property`) DO UPDATE SET `value` = ?",
entity->id,
property->id,
value_json_str,
value_json_str
)
);
}
// No existence checks here for performance reasons. All methods on IkarusObject perform this check anyway.
struct IkarusObject * ikarus_entity_to_object(IkarusEntity * entity, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(entity, nullptr);
return static_cast<IkarusObject *>(entity);
}
struct IkarusObject const * ikarus_entity_to_object_const(IkarusEntity const * entity, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(entity, nullptr);
return static_cast<IkarusObject const *>(entity);
}

View file

@ -1,5 +1,109 @@
#include "object.hpp" #include "object.hpp"
#include <fmt/format.h>
#include <ikarus/objects/object.h>
#include <errors.hpp>
#include <objects/blueprint.hpp>
#include <objects/entity.hpp>
#include <objects/properties/property.hpp>
#include <persistence/project.hpp>
IkarusObject::IkarusObject(IkarusProject * project, IkarusId id): IkarusObject::IkarusObject(IkarusProject * project, IkarusId id):
project{project}, project{project},
id{id} {} id{id} {}
IkarusProject * ikarus_object_get_project(IkarusObject const * object, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(object, nullptr);
IKARUS_FAIL_IF_OBJECT_MISSING(object, nullptr);
return object->project;
}
bool ikarus_object_is_equal(IkarusObject const * lhs, IkarusObject const * rhs, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(lhs, false);
IKARUS_FAIL_IF_OBJECT_MISSING(lhs, false);
IKARUS_FAIL_IF_NULL(rhs, false);
IKARUS_FAIL_IF_OBJECT_MISSING(rhs, false);
return lhs->id == rhs->id;
}
void ikarus_object_visit(
IkarusObject * object,
void (*blueprint_visitor)(struct IkarusBlueprint *, void *),
void (*property_visitor)(struct IkarusProperty *, void *),
void (*entity_visitor)(struct IkarusEntity *, void *),
void * data,
IkarusErrorData * error_out
) {
struct Data {
void (*blueprint_visitor)(struct IkarusBlueprint *, void *);
void (*property_visitor)(struct IkarusProperty *, void *);
void (*entity_visitor)(struct IkarusEntity *, void *);
void * data;
};
Data passthru_data{
blueprint_visitor,
property_visitor,
entity_visitor,
data,
};
ikarus_object_visit_const(
object,
[](IkarusBlueprint const * blueprint, void * data) {
auto const * passthru_data = static_cast<Data *>(data);
passthru_data->blueprint_visitor(const_cast<IkarusBlueprint *>(blueprint), passthru_data->data);
},
[](IkarusProperty const * property, void * data) {
auto const * passthru_data = static_cast<Data *>(data);
passthru_data->property_visitor(const_cast<IkarusProperty *>(property), passthru_data->data);
},
[](IkarusEntity const * entity, void * data) {
auto const * passthru_data = static_cast<Data *>(data);
passthru_data->entity_visitor(const_cast<IkarusEntity *>(entity), passthru_data->data);
},
&passthru_data,
error_out
);
}
void ikarus_object_visit_const(
IkarusObject const * object,
void (*blueprint_visitor)(struct IkarusBlueprint const *, void *),
void (*property_visitor)(struct IkarusProperty const *, void *),
void (*entity_visitor)(struct IkarusEntity const *, void *),
void * data,
IkarusErrorData * error_out
) {
IKARUS_FAIL_IF_NULL(object, );
IKARUS_FAIL_IF_OBJECT_MISSING(object, );
switch (ikarus_id_get_object_type(object->id)) {
case IkarusObjectType_Entity: {
auto const * entity = dynamic_cast<IkarusEntity const *>(object);
IKARUS_FAIL_IF(entity == nullptr, , "object with entity id wasn't a entity", IkarusErrorInfo_LibIkarus_InvalidState);
entity_visitor(entity, data);
}
case IkarusObjectType_Blueprint: {
auto const * blueprint = dynamic_cast<IkarusBlueprint const *>(object);
IKARUS_FAIL_IF(blueprint == nullptr, , "object with blueprint id wasn't a blueprint", IkarusErrorInfo_LibIkarus_InvalidState);
blueprint_visitor(blueprint, data);
}
case IkarusObjectType_Property: {
auto const * property = dynamic_cast<IkarusProperty const *>(object);
IKARUS_FAIL_IF(property == nullptr, , "object with property id wasn't a property", IkarusErrorInfo_LibIkarus_InvalidState);
property_visitor(property, data);
}
default: {
IKARUS_FAIL(
,
fmt::format("unknown object type: {}", ikarus_object_type_to_string(ikarus_id_get_object_type(object->id))),
IkarusErrorInfo_LibIkarus_InvalidState
);
}
}
}

View file

@ -1,9 +1,5 @@
#pragma once #pragma once
#include <cppbase/result.hpp>
#include <sqlitecpp/errors.hpp>
#include <ikarus/id.h> #include <ikarus/id.h>
struct IkarusObject { struct IkarusObject {

View file

@ -1,4 +1,33 @@
#include "number_property.hpp" #include "number_property.hpp"
#include <cppbase/result.hpp>
#include <ikarus/objects/properties/property.h>
#include <objects/properties/property_source.hpp>
#include <objects/properties/util.hpp>
#include <values/value.hpp>
IkarusNumberProperty::IkarusNumberProperty(IkarusProject * project, IkarusId id): IkarusNumberProperty::IkarusNumberProperty(IkarusProject * project, IkarusId id):
IkarusProperty{project, id, this} {} IkarusProperty{project, id, this} {}
IkarusNumberProperty * ikarus_number_property_create(
struct IkarusProject * project,
char const * name,
struct IkarusPropertySource * property_source,
IkarusErrorData * error_out
) {
return ikarus::util::create_property<IkarusNumberProperty>(project, name, property_source, error_out);
}
IkarusNumberValue * ikarus_number_property_get_default_value(IkarusNumberProperty * property, IkarusErrorData * error_out) {
return ikarus::util::get_default_value<IkarusNumberProperty>(property, error_out);
}
void ikarus_number_property_set_default_value(
IkarusNumberProperty * property,
IkarusNumberValue * new_default_value,
IkarusErrorData * error_out
) {
ikarus::util::set_default_value<IkarusNumberProperty>(property, new_default_value, error_out);
}

View file

@ -1,8 +1,15 @@
#pragma once #pragma once
#include <objects/properties/property.hpp> #include <ikarus/objects/properties/property_type.h>
#include <objects/properties/property.hpp>
#include <values/number_value.hpp>
struct IkarusNumberProperty : IkarusProperty {
public:
using value_type = IkarusNumberValue;
constexpr auto static PropertyType = IkarusPropertyType_Number;
struct IkarusNumberProperty final : IkarusProperty {
public: public:
IkarusNumberProperty(struct IkarusProject * project, IkarusId id); IkarusNumberProperty(struct IkarusProject * project, IkarusId id);
}; };

View file

@ -2,8 +2,10 @@
#include <cppbase/logger.hpp> #include <cppbase/logger.hpp>
#include <ikarus/objects/properties/property.h>
#include <ikarus/objects/properties/property_type.h> #include <ikarus/objects/properties/property_type.h>
#include <errors.hpp>
#include <objects/properties/property_source.hpp> #include <objects/properties/property_source.hpp>
#include <persistence/project.hpp> #include <persistence/project.hpp>
#include <sys/stat.h> #include <sys/stat.h>
@ -11,101 +13,66 @@
IkarusProperty::IkarusProperty(IkarusProject * project, IkarusId id, Data data): IkarusProperty::IkarusProperty(IkarusProject * project, IkarusId id, Data data):
IkarusObject{project, id}, IkarusObject{project, id},
_data{data} {} data{data} {}
IkarusProperty::Data & IkarusProperty::get_data() { IKA_API void ikarus_property_delete(IkarusProperty * property, IkarusErrorData * error_out) {
return _data; IKARUS_FAIL_IF_NULL(property, );
} IKARUS_FAIL_IF_OBJECT_MISSING(property, );
IkarusProperty::Data const & IkarusProperty::get_data() const { IKARUS_TRYRV_OR_FAIL(
return _data;
}
cppbase::Result<IkarusPropertyType, sqlitecpp::SingleQueryError> IkarusProperty::get_property_type(IkarusProject * project, IkarusId id) {
VTRY(
auto const type,
project->db->query_one<int>("SELECT `type` FROM `properties` WHERE `id` = ?", id).on_error([project](auto const & err) {
project->set_error(
fmt::format("failed to fetch unboxed property type: {}", err),
true,
IkarusErrorInfo_Source_SubSystem,
IkarusErrorInfo_Type_SubSystem_Database
);
})
);
return cppbase::ok(static_cast<IkarusPropertyType>(type));
}
IKA_API void ikarus_property_delete(IkarusProperty * property) {
TRYRV(
, ,
property->project->db->execute("DELETE FROM `objects` WHERE `id` = ?", property->id).on_error([property](auto const & err) { "unable to delete property: {}",
property->project->set_error( IkarusErrorInfo_Database_QueryFailed,
fmt::format("failed to delete property from objects table: {}", err), property->project->db->execute("DELETE FROM `objects` WHERE `id` = ?", property->id)
true,
IkarusErrorInfo_Source_SubSystem,
IkarusErrorInfo_Type_SubSystem_Database
);
})
); );
property->project->uncache(property); property->project->uncache(property);
} }
IkarusPropertyType ikarus_property_get_type(IkarusProperty const * property) { IkarusPropertyType ikarus_property_get_type(IkarusProperty const * property, IkarusErrorData * error_out) {
return IkarusProperty::get_property_type(property->project, property->id).unwrap_value_or(IkarusPropertyType_Toggle); IKARUS_FAIL_IF_NULL(property, IkarusPropertyType_Toggle);
IKARUS_FAIL_IF_OBJECT_MISSING(property, IkarusPropertyType_Toggle);
IKARUS_VTRYRV_OR_FAIL(
auto const ret,
IkarusPropertyType_Toggle,
"unable to fetch property type from database: {}",
IkarusErrorInfo_Database_QueryFailed,
property->project->db->query_one<int>("SELECT `type` FROM `properties` WHERE `id` = ?", property->id)
);
return static_cast<IkarusPropertyType>(ret);
} }
IkarusPropertySource const * ikarus_property_get_source(IkarusProperty const * property) { IkarusPropertySource const * ikarus_property_get_source(IkarusProperty const * property, IkarusErrorData * error_out) {
VTRYRV( IKARUS_FAIL_IF_NULL(property, nullptr);
IKARUS_FAIL_IF_OBJECT_MISSING(property, nullptr);
IKARUS_VTRYRV_OR_FAIL(
auto const source, auto const source,
nullptr, nullptr,
property->project->db "unable to fetch property source from database: {}",
->query_one<int>("SELECT `source` FROM `properties` WHERE `id` = ?", property->id) IkarusErrorInfo_Database_QueryFailed,
.on_error([property](auto const & err) { property->project->db->query_one<IkarusId>("SELECT `source` FROM `properties` WHERE `id` = ?", property->id)
property->project->set_error(
fmt::format("failed to fetch property's source: {}", err),
true,
IkarusErrorInfo_Source_SubSystem,
IkarusErrorInfo_Type_SubSystem_Database
);
})
); );
switch (ikarus_id_get_object_type(source)) { switch (ikarus_id_get_object_type(source)) {
case IkarusObjectType_Blueprint: return new IkarusPropertySource{property->project->get_blueprint(source)}; case IkarusObjectType_Blueprint: return new IkarusPropertySource{property->project->get_blueprint(source)};
case IkarusObjectType_Entity: return new IkarusPropertySource{property->project->get_entity(source)}; case IkarusObjectType_Entity: return new IkarusPropertySource{property->project->get_entity(source)};
default: { default:
property->project->set_error( IKARUS_FAIL(
fmt::format("PropertySource is neither blueprint nor entity"),
true,
IkarusErrorInfo_Source_LibIkarus,
IkarusErrorInfo_Type_LibIkarus_InvalidState
);
return nullptr;
}
}
}
IkarusValue * ikarus_property_get_default_value(IkarusProperty const * property) {
VTRYRV(
auto const value,
nullptr, nullptr,
property->project->db fmt::format("invalid property source type: {}", ikarus_object_type_to_string(ikarus_id_get_object_type(source))),
->query_one<int>("SELECT `default_value` FROM `properties` WHERE `id` = ?", property->id) IkarusErrorInfo_LibIkarus_InvalidState
.on_error([property](auto const & err) {
property->project->set_error(
fmt::format("failed to fetch property's default value: {}", err),
true,
IkarusErrorInfo_Source_SubSystem,
IkarusErrorInfo_Type_SubSystem_Database
);
})
); );
}
}
return IkarusValue::from_json(value).unwrap_value_or(nullptr); IkarusValue * ikarus_property_get_default_value(IkarusProperty const * property, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(property, nullptr);
IKARUS_FAIL_IF_OBJECT_MISSING(property, nullptr);
return fetch_value_from_db(property->project, error_out, "SELECT `default_value` FROM `properties` WHERE `id` = ?", property->id);
} }
void ikarus_property_visit( void ikarus_property_visit(
@ -113,15 +80,19 @@ void ikarus_property_visit(
void (*toggle_property_visitor)(struct IkarusToggleProperty *, void *), void (*toggle_property_visitor)(struct IkarusToggleProperty *, void *),
void (*number_property_visitor)(struct IkarusNumberProperty *, void *), void (*number_property_visitor)(struct IkarusNumberProperty *, void *),
void (*text_property_visitor)(struct IkarusTextProperty *, void *), void (*text_property_visitor)(struct IkarusTextProperty *, void *),
void * data void * data,
IkarusErrorData * error_out
) { ) {
IKARUS_FAIL_IF_NULL(property, );
IKARUS_FAIL_IF_OBJECT_MISSING(property, );
std::visit( std::visit(
cppbase::overloaded{ cppbase::overloaded{
[toggle_property_visitor, data](IkarusToggleProperty * property) { toggle_property_visitor(property, data); }, [toggle_property_visitor, data](IkarusToggleProperty * property) { toggle_property_visitor(property, data); },
[number_property_visitor, data](IkarusNumberProperty * property) { number_property_visitor(property, data); }, [number_property_visitor, data](IkarusNumberProperty * property) { number_property_visitor(property, data); },
[text_property_visitor, data](IkarusTextProperty * property) { text_property_visitor(property, data); } [text_property_visitor, data](IkarusTextProperty * property) { text_property_visitor(property, data); }
}, },
property->get_data() property->data
); );
} }
@ -130,18 +101,24 @@ void ikarus_property_visit_const(
void (*toggle_property_visitor)(struct IkarusToggleProperty const *, void *), void (*toggle_property_visitor)(struct IkarusToggleProperty const *, void *),
void (*number_property_visitor)(struct IkarusNumberProperty const *, void *), void (*number_property_visitor)(struct IkarusNumberProperty const *, void *),
void (*text_property_visitor)(struct IkarusTextProperty const *, void *), void (*text_property_visitor)(struct IkarusTextProperty const *, void *),
void * data void * data,
IkarusErrorData * error_out
) { ) {
IKARUS_FAIL_IF_NULL(property, );
IKARUS_FAIL_IF_OBJECT_MISSING(property, );
std::visit( std::visit(
cppbase::overloaded{ cppbase::overloaded{
[toggle_property_visitor, data](IkarusToggleProperty const * property) { toggle_property_visitor(property, data); }, [toggle_property_visitor, data](IkarusToggleProperty const * property) { toggle_property_visitor(property, data); },
[number_property_visitor, data](IkarusNumberProperty const * property) { number_property_visitor(property, data); }, [number_property_visitor, data](IkarusNumberProperty const * property) { number_property_visitor(property, data); },
[text_property_visitor, data](IkarusTextProperty const * property) { text_property_visitor(property, data); } [text_property_visitor, data](IkarusTextProperty const * property) { text_property_visitor(property, data); }
}, },
property->get_data() property->data
); );
} }
// No existence checks here for performance reasons. All methods on IkarusObject perform this check anyway.
IkarusObject * ikarus_property_to_object(IkarusProperty * property) { IkarusObject * ikarus_property_to_object(IkarusProperty * property) {
return static_cast<IkarusObject *>(property); return static_cast<IkarusObject *>(property);
} }

View file

@ -2,23 +2,12 @@
#include <variant> #include <variant>
#include <cppbase/result.hpp>
#include <sqlitecpp/errors.hpp>
#include <ikarus/objects/properties/property_type.h>
#include <objects/object.hpp> #include <objects/object.hpp>
struct IkarusProperty : IkarusObject { struct IkarusProperty : IkarusObject {
public: public:
using Data = std::variant<struct IkarusToggleProperty *, struct IkarusNumberProperty *, struct IkarusTextProperty *>; using Data = std::variant<struct IkarusToggleProperty *, struct IkarusNumberProperty *, struct IkarusTextProperty *>;
public:
/// \brief Helper to fetch a type for a property that isn't yet wrapped in an object
[[nodiscard]] static cppbase::Result<IkarusPropertyType, sqlitecpp::SingleQueryError>
get_property_type(struct IkarusProject * project, IkarusId id);
public: public:
IkarusProperty(struct IkarusProject * project, IkarusId id, Data data); IkarusProperty(struct IkarusProject * project, IkarusId id, Data data);
@ -31,9 +20,5 @@ public:
~IkarusProperty() override = default; ~IkarusProperty() override = default;
public: public:
[[nodiscard]] Data & get_data(); Data data;
[[nodiscard]] Data const & get_data() const;
private:
Data _data;
}; };

View file

@ -3,10 +3,22 @@
#include <boost/functional/overloaded_function.hpp> #include <boost/functional/overloaded_function.hpp>
#include <objects/blueprint.hpp> #include <objects/blueprint.hpp>
#include <objects/entity.hpp>
#include <cppbase/templates.hpp>
IkarusPropertySource::IkarusPropertySource(Data data): IkarusPropertySource::IkarusPropertySource(Data data):
data{data} {} data{data} {}
IkarusId IkarusPropertySource::get_id() const {
return boost::variant2::visit(
cppbase::overloaded {
[](IkarusBlueprint const * blueprint) { return blueprint->id; },
[](IkarusEntity const * entity) { return entity->id; }
},
data
);
}
IkarusPropertySource * ikarus_property_source_create_blueprint(IkarusBlueprint * blueprint) { IkarusPropertySource * ikarus_property_source_create_blueprint(IkarusBlueprint * blueprint) {
return new IkarusPropertySource{blueprint}; return new IkarusPropertySource{blueprint};
} }

View file

@ -1,7 +1,7 @@
#pragma once #pragma once
#include <boost/variant2.hpp> #include <boost/variant2.hpp>
#include <ikarus/id.h>
#include <ikarus/objects/properties/property_source.h> #include <ikarus/objects/properties/property_source.h>
struct IkarusPropertySource { struct IkarusPropertySource {
@ -19,6 +19,9 @@ public:
virtual ~IkarusPropertySource() = default; virtual ~IkarusPropertySource() = default;
public:
[[nodiscard]] IkarusId get_id() const;
public: public:
Data data; Data data;
}; };

View file

@ -1,4 +1,33 @@
#include "text_property.hpp" #include "text_property.hpp"
#include <cppbase/result.hpp>
#include <ikarus/objects/properties/property.h>
#include <objects/properties/property_source.hpp>
#include <objects/properties/util.hpp>
#include <values/value.hpp>
IkarusTextProperty::IkarusTextProperty(IkarusProject * project, IkarusId id): IkarusTextProperty::IkarusTextProperty(IkarusProject * project, IkarusId id):
IkarusProperty{project, id, this} {} IkarusProperty{project, id, this} {}
IkarusTextProperty * ikarus_text_property_create(
struct IkarusProject * project,
char const * name,
struct IkarusPropertySource * property_source,
IkarusErrorData * error_out
) {
return ikarus::util::create_property<IkarusTextProperty>(project, name, property_source, error_out);
}
IkarusTextValue * ikarus_text_property_get_default_value(IkarusTextProperty * property, IkarusErrorData * error_out) {
return ikarus::util::get_default_value<IkarusTextProperty>(property, error_out);
}
void ikarus_text_property_set_default_value(
IkarusTextProperty * property,
IkarusTextValue * new_default_value,
IkarusErrorData * error_out
) {
ikarus::util::set_default_value<IkarusTextProperty>(property, new_default_value, error_out);
}

View file

@ -1,8 +1,15 @@
#pragma once #pragma once
#include <objects/properties/property.hpp> #include <ikarus/objects/properties/property_type.h>
#include <objects/properties/property.hpp>
#include <values/text_value.hpp>
struct IkarusTextProperty : IkarusProperty {
public:
using value_type = IkarusTextValue;
constexpr auto static PropertyType = IkarusPropertyType_Text;
struct IkarusTextProperty final : IkarusProperty {
public: public:
IkarusTextProperty(struct IkarusProject * project, IkarusId id); IkarusTextProperty(struct IkarusProject * project, IkarusId id);
}; };

View file

@ -1,7 +1,33 @@
#include "toggle_property.hpp" #include "toggle_property.hpp"
#include <cppbase/result.hpp>
#include <ikarus/objects/properties/property.h>
#include <objects/properties/property_source.hpp>
#include <objects/properties/util.hpp>
#include <values/value.hpp>
IkarusToggleProperty::IkarusToggleProperty(IkarusProject * project, IkarusId id): IkarusToggleProperty::IkarusToggleProperty(IkarusProject * project, IkarusId id):
IkarusProperty{project, id, this} {} IkarusProperty{project, id, this} {}
IkarusToggleProperty * IkarusToggleProperty * ikarus_toggle_property_create(
ikarus_toggle_property_create(struct IkarusProject * project, char const * name, struct IkarusPropertySource * property_source) {} struct IkarusProject * project,
char const * name,
struct IkarusPropertySource * property_source,
IkarusErrorData * error_out
) {
return ikarus::util::create_property<IkarusToggleProperty>(project, name, property_source, error_out);
}
IkarusToggleValue * ikarus_toggle_property_get_default_value(struct IkarusToggleProperty * property, IkarusErrorData * error_out) {
return ikarus::util::get_default_value<IkarusToggleProperty>(property, error_out);
}
void ikarus_toggle_property_set_default_value(
struct IkarusToggleProperty * property,
struct IkarusToggleValue * new_default_value,
IkarusErrorData * error_out
) {
ikarus::util::set_default_value<IkarusToggleProperty>(property, new_default_value, error_out);
}

View file

@ -1,8 +1,15 @@
#pragma once #pragma once
#include <ikarus/objects/properties/property_type.h>
#include <objects/properties/property.hpp> #include <objects/properties/property.hpp>
#include <values/toggle_value.hpp>
struct IkarusToggleProperty : IkarusProperty { struct IkarusToggleProperty : IkarusProperty {
public:
using value_type = IkarusToggleValue;
constexpr auto static PropertyType = IkarusPropertyType_Toggle;
public: public:
IkarusToggleProperty(struct IkarusProject * project, IkarusId id); IkarusToggleProperty(struct IkarusProject * project, IkarusId id);
}; };

View file

@ -0,0 +1,100 @@
#pragma once
#include <boost/type_index.hpp>
#include <cppbase/strings.hpp>
#include <ikarus/objects/properties/property.h>
#include <errors.hpp>
#include <objects/properties/property.hpp>
#include <objects/properties/property_source.hpp>
#include <persistence/project.hpp>
#include <values/value.hpp>
namespace ikarus::util {
template<typename T>
T * create_property(
struct IkarusProject * project,
char const * name,
struct IkarusPropertySource * property_source,
IkarusErrorData * error_out
) {
IKARUS_FAIL_IF_NULL(project, nullptr);
IKARUS_FAIL_IF_NULL(name, nullptr);
IKARUS_FAIL_IF_NULL(property_source, nullptr);
IKARUS_FAIL_IF(
cppbase::is_empty_or_blank(name),
nullptr,
fmt::format("{} name cannot be empty or blank", boost::typeindex::type_id<T>().pretty_name()),
IkarusErrorInfo_Client_InvalidInput
)
IKARUS_VTRYRV_OR_FAIL(
IkarusId const id,
nullptr,
"failed to create property: {}",
IkarusErrorInfo_Database_QueryFailed,
project->db->transact([name, property_source](auto * db) -> cppbase::Result<IkarusId, sqlitecpp::TransactionError> {
TRY(db->execute("INSERT INTO `objects`(`type`, `name`) VALUES(?, ?, ?)", IkarusObjectType_Property, name));
auto id = ikarus_id_from_data_and_type(db->last_insert_rowid(), IkarusObjectType_Property);
TRY(db->execute(
"INSERT INTO `properties`(`id`, `type`, `source`) VALUES(?, ?, ?)",
id,
T::PropertyType,
property_source->get_id()
));
return cppbase::ok(id);
})
);
auto * ret = dynamic_cast<T *>(project->get_property(id, T::PropertyType));
IKARUS_FAIL_IF(
ret == nullptr,
nullptr,
fmt::format("created {} cannot be casted down from IkarusProject", boost::typeindex::type_id<T>().pretty_name()),
IkarusErrorInfo_LibIkarus_InvalidState
);
return ret;
}
template<typename T>
typename T::value_type * get_default_value(IkarusProperty const * property, IkarusErrorData * error_out) {
auto * value = ikarus_property_get_default_value(property, error_out);
IKARUS_FAIL_IF_ERROR(nullptr);
auto * ret = boost::variant2::get_if<typename T::value_type *>(&value->data);
IKARUS_FAIL_IF(
ret == nullptr,
nullptr,
fmt::format(
"{} default value is not a(n) {}",
boost::typeindex::type_id<T>().pretty_name(),
boost::typeindex::type_id<typename T::value_type>().pretty_name()
),
IkarusErrorInfo_Database_InvalidState
);
return *ret;
}
template<typename T>
void set_default_value(T * property, typename T::value_type * new_default_value, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(property, );
IKARUS_FAIL_IF_OBJECT_MISSING(property, );
IKARUS_FAIL_IF_NULL(new_default_value, );
auto value_json_str = boost::json::serialize(new_default_value->to_json());
IKARUS_TRYRV_OR_FAIL(
,
"unable to set property default value: {}",
IkarusErrorInfo_Database_QueryFailed,
property->project->db->execute("UPDATE `properties` SET `default_value` = ? WHERE `id` = ?", value_json_str, property->id)
);
}
} // namespace ikarus::util

View file

@ -0,0 +1,40 @@
#pragma once
#include <array>
#include <cppbase/assets.hpp>
#include <cppbase/result.hpp>
#include <sqlitecpp/connection.hpp>
namespace ikarus {
CPPBASE_ASSET(m1_initial_layout, "persistence/migrations/m1_initial_layout.sql");
class Migration : public sqlitecpp::Migration {
public:
Migration(char const * sql, size_t size):
sql{sql, size} {}
~Migration() override = default;
public:
[[nodiscard]] inline auto get_content() const -> std::string_view override {
return sql;
}
public:
std::string_view sql;
};
#define DECLARE_MIGRATION(name) std::make_unique<ikarus::Migration>(static_cast<char const *>(name()), name##_size())
constexpr std::string_view DB_VERSION_KEY = "IKARUS_DB_VERSION";
std::vector<std::unique_ptr<sqlitecpp::Migration>> const MIGRATIONS = []() {
std::vector<std::unique_ptr<sqlitecpp::Migration>> ret;
ret.emplace_back(DECLARE_MIGRATION(m1_initial_layout));
return ret;
}();
} // namespace ikarus

View file

@ -0,0 +1,7 @@
CREATE TABLE `metadata`
(
`key` VARCHAR(255) NOT NULL,
`value` VARCHAR(255) NOT NULL,
PRIMARY KEY (`key`)
)

View file

@ -1,6 +1,12 @@
#include "project.hpp" #include "project.hpp"
#include "ikarus/persistence/project.h" #include "migrations.hpp"
#include <boost/filesystem.hpp>
#include <cppbase/strings.hpp>
#include <ikarus/persistence/project.h>
#include <objects/blueprint.hpp> #include <objects/blueprint.hpp>
#include <objects/entity.hpp> #include <objects/entity.hpp>
@ -9,10 +15,10 @@
#include <objects/properties/text_property.hpp> #include <objects/properties/text_property.hpp>
#include <objects/properties/toggle_property.hpp> #include <objects/properties/toggle_property.hpp>
IkarusProject::IkarusProject(std::string_view name, std::filesystem::path path): IkarusProject::IkarusProject(std::string_view name, std::string_view path, std::unique_ptr<sqlitecpp::Connection> && db):
name{name}, name{name},
path{path}, path{std::move(path)},
db{nullptr}, db{std::move(db)},
_blueprints{}, _blueprints{},
_properties{}, _properties{},
_entities{} {} _entities{} {}
@ -52,3 +58,222 @@ auto IkarusProject::get_property(IkarusId id, IkarusPropertyType type) -> Ikarus
auto IkarusProject::uncache(IkarusProperty * property) -> void { auto IkarusProject::uncache(IkarusProperty * property) -> void {
remove_cached_object(property, _properties); remove_cached_object(property, _properties);
} }
IkarusProject * ikarus_project_create(char const * path, char const * name, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(path, nullptr);
IKARUS_FAIL_IF_NULL(name, nullptr);
IKARUS_FAIL_IF(cppbase::is_empty_or_blank(path), nullptr, "path must not be empty", IkarusErrorInfo_Client_InvalidInput);
IKARUS_FAIL_IF(cppbase::is_empty_or_blank(name), nullptr, "name must not be empty", IkarusErrorInfo_Client_InvalidInput);
boost::filesystem::path fs_path{path};
{
boost::system::error_code ec;
bool const exists = fs::exists(fs_path, ec);
IKARUS_FAIL_IF(ec, nullptr, "unable to check whether path is occupied", IkarusErrorInfo_Filesystem_AlreadyExists);
IKARUS_FAIL_IF(exists, nullptr, "path is already occupied", IkarusErrorInfo_Filesystem_AlreadyExists);
}
IKARUS_VTRYRV_OR_FAIL(
auto db,
nullptr,
"failed to create project db: {}",
IkarusErrorInfo_Database_ConnectionFailed,
sqlitecpp::Connection::open(path)
);
IKARUS_TRYRV_OR_FAIL(
nullptr,
"failed to migrate project db: {}",
IkarusErrorInfo_Database_MigrationFailed,
db->migrate(ikarus::MIGRATIONS)
);
if (auto res = db->execute("INSERT INTO `metadata`(`key`, `value`) VALUES(?, ?)", DB_PROJECT_NAME_KEY, name); res.is_error()) {
boost::system::error_code ec;
fs::remove(fs_path, ec);
IKARUS_FAIL_IF(
ec,
nullptr,
"failed to remove project db after being unable to insert name into metadata table: {}",
IkarusErrorInfo_Filesystem_AccessIssue
);
IKARUS_FAIL(nullptr, "failed to insert project name into metadata: {}", IkarusErrorInfo_Database_QueryFailed);
}
return new IkarusProject{name, path, std::move(db)};
}
IkarusProject * ikarus_project_create_in_memory(char const * name, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(name, nullptr);
IKARUS_FAIL_IF(cppbase::is_empty_or_blank(name), nullptr, "name must not be empty", IkarusErrorInfo_Client_InvalidInput);
IKARUS_VTRYRV_OR_FAIL(
auto db,
nullptr,
"failed to create project db in memory: {}",
IkarusErrorInfo_Database_ConnectionFailed,
sqlitecpp::Connection::open_in_memory()
);
IKARUS_TRYRV_OR_FAIL(
nullptr,
"failed to migrate project db: {}",
IkarusErrorInfo_Database_MigrationFailed,
db->migrate(ikarus::MIGRATIONS)
);
IKARUS_TRYRV_OR_FAIL(
nullptr,
"failed to insert project name into metadata: {}",
IkarusErrorInfo_Database_QueryFailed,
db->execute("INSERT INTO `metadata`(`key`, `value`) VALUES(?, ?)", DB_PROJECT_NAME_KEY, name)
);
return new IkarusProject{name, ":memory:", std::move(db)};
}
IkarusProject * ikarus_project_open(char const * path, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(path, nullptr);
IKARUS_FAIL_IF(cppbase::is_empty_or_blank(path), nullptr, "path must not be empty", IkarusErrorInfo_Client_InvalidInput);
IKARUS_VTRYRV_OR_FAIL(
auto db,
nullptr,
"failed to open project db: {}",
IkarusErrorInfo_Database_ConnectionFailed,
sqlitecpp::Connection::open(path)
);
IKARUS_TRYRV_OR_FAIL(
nullptr,
"failed to migrate project db: {}",
IkarusErrorInfo_Database_MigrationFailed,
db->migrate(ikarus::MIGRATIONS)
);
IKARUS_VTRYRV_OR_FAIL(
auto const & name,
nullptr,
"failed to retrieve project name from metadata: {}",
IkarusErrorInfo_Database_QueryFailed,
db->query_one<std::string>("SELECT `value` FROM `metadata` WHERE `key` = ?", DB_PROJECT_NAME_KEY)
);
return new IkarusProject{name, path, std::move(db)};
}
char const * ikarus_project_get_name(IkarusProject const * project, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(project, nullptr);
return project->name.data();
}
void ikarus_project_set_name(IkarusProject * project, char const * new_name, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(project, );
IKARUS_FAIL_IF_NULL(new_name, );
IKARUS_FAIL_IF(cppbase::is_empty_or_blank(new_name), , "name must not be empty", IkarusErrorInfo_Client_InvalidInput);
IKARUS_TRYRV_OR_FAIL(
,
"failed to update project name: {}",
IkarusErrorInfo_Database_QueryFailed,
project->db->execute("UPDATE `metadata` SET `value` = ? WHERE `key` = ?", new_name, DB_PROJECT_NAME_KEY)
);
}
char const * ikarus_project_get_path(IkarusProject const * project, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(project, nullptr);
return project->path.data();
}
// these take a mutable project right now because the get_cached-* function must be mutable
// since we insert a backreference to the project into the objects, not ideal,
// but fine for now since "mutability" is a vague concept for projects anyway
void ikarus_project_get_blueprints(
IkarusProject * project,
struct IkarusBlueprint ** blueprints_out,
size_t blueprints_out_size,
IkarusErrorData * error_out
) {
IKARUS_FAIL_IF_NULL(project, );
IKARUS_FAIL_IF_NULL(blueprints_out, );
if (blueprints_out_size == 0) {
return;
}
IkarusId ids[blueprints_out_size];
IKARUS_TRYRV_OR_FAIL(
,
"unable to fetch project blueprints from database: {}",
IkarusErrorInfo_Database_QueryFailed,
project->db->query_many_buffered<IkarusId>("SELECT `id` FROM `blueprints`", ids, blueprints_out_size)
);
for (size_t i = 0; i < blueprints_out_size; ++i) {
blueprints_out[i] = project->get_blueprint(ids[i]);
}
}
size_t ikarus_project_get_blueprint_count(IkarusProject const * project, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(project, 0);
IKARUS_VTRYRV_OR_FAIL(
auto const ret,
0,
"unable to fetch project blueprint count from database: {}",
IkarusErrorInfo_Database_QueryFailed,
project->db->query_one<int64_t>("SELECT COUNT(*) FROM `blueprints`")
);
return ret;
}
void ikarus_project_get_entities(
IkarusProject * project,
struct IkarusEntity ** entities_out,
size_t entities_out_size,
IkarusErrorData * error_out
) {
IKARUS_FAIL_IF_NULL(project, );
IKARUS_FAIL_IF_NULL(entities_out, );
if (entities_out_size == 0) {
return;
}
IkarusId ids[entities_out_size];
IKARUS_TRYRV_OR_FAIL(
,
"unable to fetch project entities from database: {}",
IkarusErrorInfo_Database_QueryFailed,
project->db->query_many_buffered<IkarusId>("SELECT `id` FROM `entities`", ids, entities_out_size)
);
for (size_t i = 0; i < entities_out_size; ++i) {
entities_out[i] = project->get_entity(ids[i]);
}
}
size_t ikarus_project_get_entity_count(IkarusProject * project, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(project, 0);
IKARUS_VTRYRV_OR_FAIL(
auto const ret,
0,
"unable to fetch project entity count from database: {}",
IkarusErrorInfo_Database_QueryFailed,
project->db->query_one<int64_t>("SELECT COUNT(*) FROM `entities`")
);
return ret;
}

View file

@ -1,20 +1,23 @@
#pragma once #pragma once
#include <filesystem>
#include <ranges> #include <ranges>
#include <stack> #include <stack>
#include <string> #include <string>
#include <boost/filesystem.hpp>
#include <sqlitecpp/connection.hpp> #include <sqlitecpp/connection.hpp>
#include <ikarus/errors.h> #include <ikarus/errors.h>
#include <ikarus/id.h> #include <ikarus/id.h>
#include <ikarus/objects/properties/property_type.h> #include <ikarus/objects/properties/property_type.h>
namespace fs = boost::filesystem;
/// \private /// \private
struct IkarusProject { struct IkarusProject {
public: public:
IkarusProject(std::string_view name, std::filesystem::path path); IkarusProject(std::string_view name, std::string_view path, std::unique_ptr<sqlitecpp::Connection> && db);
public: public:
[[nodiscard]] auto get_blueprint(IkarusId id) -> struct IkarusBlueprint *; [[nodiscard]] auto get_blueprint(IkarusId id) -> struct IkarusBlueprint *;
@ -23,6 +26,7 @@ public:
[[nodiscard]] auto get_entity(IkarusId id) -> struct IkarusEntity *; [[nodiscard]] auto get_entity(IkarusId id) -> struct IkarusEntity *;
auto uncache(struct IkarusEntity * entity) -> void; auto uncache(struct IkarusEntity * entity) -> void;
// TODO improve this to take a template param so that we don't have to cast in e.g. ikarus_toggle_property_create
[[nodiscard]] auto get_property(IkarusId id, IkarusPropertyType type) -> struct IkarusProperty *; [[nodiscard]] auto get_property(IkarusId id, IkarusPropertyType type) -> struct IkarusProperty *;
auto uncache(struct IkarusProperty * property) -> void; auto uncache(struct IkarusProperty * property) -> void;
@ -45,11 +49,13 @@ private:
public: public:
std::string name; std::string name;
std::filesystem::path path; std::string_view path;
std::unique_ptr<sqlitecpp::Connection> db; std::unique_ptr<sqlitecpp::Connection> db;
private: private:
std::unordered_map<IkarusId, std::unique_ptr<struct IkarusBlueprint>> _blueprints; std::unordered_map<IkarusId, std::unique_ptr<struct IkarusBlueprint>> mutable _blueprints;
std::unordered_map<IkarusId, std::unique_ptr<struct IkarusProperty>> _properties; std::unordered_map<IkarusId, std::unique_ptr<struct IkarusProperty>> mutable _properties;
std::unordered_map<IkarusId, std::unique_ptr<struct IkarusEntity>> _entities; std::unordered_map<IkarusId, std::unique_ptr<struct IkarusEntity>> mutable _entities;
}; };
constexpr std::string_view DB_PROJECT_NAME_KEY = "PROJECT_NAME";

View file

@ -0,0 +1,21 @@
#include "values/entity_property_value.hpp"
#include <errors.hpp>
IkarusEntity const * ikarus_entity_property_value_get_entity(IkarusEntityPropertyValue const * value, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(value, nullptr);
return value->entity;
}
IkarusProperty const * ikarus_entity_property_value_get_property(IkarusEntityPropertyValue const * value, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(value, nullptr);
return value->property;
}
IkarusValue const * ikarus_entity_property_value_get_value(IkarusEntityPropertyValue const * value, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(value, nullptr);
return value->value;
}

View file

@ -0,0 +1,7 @@
#pragma once
struct IkarusEntityPropertyValue {
struct IkarusEntity const * entity;
struct IkarusProperty const * property;
struct IkarusValue const * value;
};

View file

@ -19,7 +19,7 @@
IkarusValue::IkarusValue(Data data): IkarusValue::IkarusValue(Data data):
data(data) {} data(data) {}
cppbase::Result<IkarusValue *, IkarusValue::FromJsonError> IkarusValue::from_json(boost::json::value const & json) { cppbase::Result<IkarusValue *, IkarusValue::FromJsonError> IkarusValue::from_json(boost::json::value json) {
if (auto const * obj = json.if_object(); obj == nullptr) { if (auto const * obj = json.if_object(); obj == nullptr) {
return cppbase::err(FromJsonError{}); return cppbase::err(FromJsonError{});
} else { } else {

View file

@ -5,6 +5,9 @@
#include <cppbase/result.hpp> #include <cppbase/result.hpp>
#include <errors.hpp>
#include <persistence/project.hpp>
struct IkarusValue { struct IkarusValue {
public: public:
using Data = boost::variant2::variant<struct IkarusToggleValue *, struct IkarusNumberValue *, struct IkarusTextValue *>; using Data = boost::variant2::variant<struct IkarusToggleValue *, struct IkarusNumberValue *, struct IkarusTextValue *>;
@ -24,9 +27,50 @@ public:
public: public:
struct FromJsonError {}; struct FromJsonError {};
[[nodiscard]] static cppbase::Result<IkarusValue *, FromJsonError> from_json(boost::json::value const & json); [[nodiscard]] static cppbase::Result<IkarusValue *, FromJsonError> from_json(boost::json::value json);
[[nodiscard]] boost::json::value to_json() const; [[nodiscard]] boost::json::value to_json() const;
public: public:
Data data; Data data;
}; };
template<>
struct fmt::formatter<IkarusValue::FromJsonError> {
template<typename FormatParseContext>
constexpr static auto parse(FormatParseContext & ctx) {
return ctx.end();
}
constexpr static auto format([[maybe_unused]]IkarusValue::FromJsonError const & error, fmt::format_context & ctx) {
return fmt::format_to(ctx.out(), "unable to parse ikarus value JSON");
}
};
template<typename... Args>
IkarusValue * fetch_value_from_db(IkarusProject * project, IkarusErrorData * error_out, std::string_view query, Args &&... args) {
IKARUS_VTRYRV_OR_FAIL(
auto const value_str,
nullptr,
"unable to fetch entity property value from database: {}",
IkarusErrorInfo_Database_QueryFailed,
project->db->query_one<std::string>(query, std::forward<Args>(args)...)
);
boost::json::error_code ec{};
boost::json::value json_value = boost::json::parse(value_str, ec);
if (ec) {
IKARUS_SET_ERROR(fmt::format("invalid json is stored in database: {}", ec.message()), IkarusErrorInfo_Database_InvalidState);
return nullptr;
}
IKARUS_VTRYRV_OR_FAIL(
auto const ret,
nullptr,
"unable to fetch entity property value: {}",
IkarusErrorInfo_LibIkarus_CannotPerformOperation,
IkarusValue::from_json(std::move(json_value))
);
return ret;
}

2
vendor/sqlitecpp vendored

@ -1 +1 @@
Subproject commit 00a1afcc5f564f562c436f1ddfa4f44bb6489b17 Subproject commit e837d64405fb43adefe03994837f5ed5793138c5