commit ca4d4a5dbe0f9ee3d89b595d1a6b44c6986463a5 Author: Folling Date: Tue Aug 22 00:27:22 2023 +0200 a new beginning Signed-off-by: Folling diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..0372fa7 --- /dev/null +++ b/.clang-format @@ -0,0 +1,222 @@ +BasedOnStyle: Google + +AccessModifierOffset: -4 + +AlignAfterOpenBracket: BlockIndent +AlignArrayOfStructures: Right +AlignConsecutiveAssignments: + Enabled: false +AlignConsecutiveBitFields: + Enabled: false +AlignConsecutiveDeclarations: + Enabled: false +AlignConsecutiveMacros: AcrossEmptyLines +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: true + +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: true +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: true + +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes + +BinPackArguments: false +BinPackParameters: false + +BitFieldColonSpacing: Both + +BraceWrapping: + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: MultiLine + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyNamespace: false + SplitEmptyRecord: false + +BreakAfterAttributes: Never +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeConceptDeclarations: Always +# BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: false +BreakConstructorInitializers: AfterColon +BreakInheritanceList: AfterColon +BreakStringLiterals: false + +ColumnLimit: 128 + +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 + +Cpp11BracedListStyle: true + +DerivePointerAlignment: false + +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: Always + +FixNamespaceComments: false + +IncludeBlocks: Regroup +IncludeCategories: + # Relative Includes + # "blubb.h" / "blubb/blubber.h" + - Regex: '^".+\.(h|hpp)"$' + Priority: 1 + + # C Includes + # + - Regex: '^<[a-z0-9_]+\.h>$' + Priority: 2 + + # C++ Includes + # + - Regex: '^<[a-z0-9_]+>$' + Priority: 3 + + # expected + # + - Regex: '^$' + Priority: 4 + + # libfmt + # + - Regex: '^$' + Priority: 5 + + # nlohmann::json + # + - Regex: '^$' + Priority: 6 + + # ranges + # + - Regex: '^$' + Priority: 7 + + # ICU + # + - Regex: '^$' + Priority: 8 + + # ranges + # + - Regex: '^$' + Priority: 9 + + # ranges + # + - Regex: '^$' + Priority: 10 + + # ranges + # + - Regex: '^$' + Priority: 11 + + # ranges + # + - Regex: '^$' + Priority: 12 + + # ranges + # ranges + # + - Regex: '^$' + Priority: 13 + +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentExternBlock: NoIndent +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertBraces: true +InsertNewlineAtEOF: true + +# InsertNewlineAtEOF: true +# IntegerLiteralSeparator: +# Binary: 0 +# Decimal: 3 +# Hex: -1 + +KeepEmptyLinesAtTheStartOfBlocks: false + +LambdaBodyIndentation: Signature +Language: Cpp + +# LineEnding: LF + +MaxEmptyLinesToKeep: 1 + +NamespaceIndentation: None + +PackConstructorInitializers: Never + +PointerAlignment: Middle +QualifierAlignment: Right +# QualifierOrder: [ 'friend', 'constexpr', 'inline', 'static', 'type', 'const', 'volatile' ] +ReferenceAlignment: Left + +ReflowComments: true +# RemoveSemicolon: true + +RequiresClausePosition: OwnLine +# RequiresExpressionIndentation: OuterScope + +SeparateDefinitionBlocks: Always + +SortIncludes: CaseInsensitive +SortUsingDeclarations: true + +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false + +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: false +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: 1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: c++20 + +TabWidth: 4 +UseTab: Never diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f7ab8cb --- /dev/null +++ b/.gitignore @@ -0,0 +1,58 @@ +# C++ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +### CMake ### +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +### CMake Patch ### +# External projects +*-prefix/ + +build +docs/generated +gen/script_old/target +include/ikarus/entities +include/ikarus/project.h +src/generated/**/*.cpp +src/generated/**/*.hpp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a26cc62 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "vendor/catch2"] + path = vendor/catch2 + url = git@github.com:catchorg/Catch2.git +[submodule "vendor/sqlitecpp"] + path = vendor/sqlitecpp + url = ssh://git@git.rewritesarebliss.com:16658/Folling/sqlitecpp.git +[submodule "vendor/doxygen-awesome-css"] + path = vendor/doxygen-awesome-css + url = git@github.com:jothepro/doxygen-awesome-css.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a7faf39 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,55 @@ +cmake_minimum_required(VERSION 3.18) +project(ikarus) + +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_STANDARD 20) + +set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) + +add_subdirectory(vendor) +add_subdirectory(include) +add_subdirectory(src) + +add_library( + libikarus OBJECT + ${INCLUDE_FILES} + ${SOURCE_FILES} +) + +target_include_directories( + libikarus PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/include +) + +target_include_directories( + libikarus PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/src +) + +target_link_libraries( + libikarus PUBLIC + Catch2::Catch2WithMain +) + +target_link_libraries( + libikarus PRIVATE + cppbase + sqlitecpp +) + +set_target_properties( + libikarus PROPERTIES + LINKER_LANGUAGE CXX +) + +add_executable(ikarus_tests ${SOURCE_FILES}) +target_link_libraries(ikarus_tests PRIVATE Catch2::Catch2WithMain) + +target_include_directories( + ikarus_tests PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/include +) + +include(CTest) +include(vendor/catch2/extras/Catch.cmake) +catch_discover_tests(ikarus_tests) diff --git a/README.md b/README.md new file mode 100644 index 0000000..57b6325 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +### Data Longevity + +All data returned by libikarus is ephemeral and only represents the state of the project at the time of the request. +A snapshot if you will. +One must not rely on it representing the actual state of the project at any given time. The data is simply copied +from the underlying data sources and returned to the caller. + +No mechanisms are provided to avoid race conditions. LibIkarus itself should only be used in a single-threaded context. +However, nothing breaks if you do use it in a multithreaded context, that is, libikarus is threadsafe. +You just cannot rely on the data being consistent. +This goes especially for inter-process access to the same project. \ No newline at end of file diff --git a/docs/DoxyFile b/docs/DoxyFile new file mode 100644 index 0000000..61f3460 --- /dev/null +++ b/docs/DoxyFile @@ -0,0 +1,16 @@ +DISABLE_INDEX = NO +EXCLUDE = ../vendor ../build +EXCLUDE_PATTERNS = cmake-* +FILE_PATTERNS = *.h *.hpp *.tpp *.ipp *.cpp +FULL_SIDEBAR = NO +GENERATE_LATEX = NO +GENERATE_TREEVIEW = YES +HTML_COLORSTYLE = LIGHT # required with Doxygen >= 1.9.5 +HTML_EXTRA_FILES = ../vendor/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js ../vendor/doxygen-awesome-css/doxygen-awesome-fragment-copy-button.js ./enum_format_fix.js +HTML_EXTRA_STYLESHEET = ../vendor/doxygen-awesome-css/doxygen-awesome.css +HTML_HEADER = header.html +INPUT = .. +OUTPUT_DIRECTORY = generated +PROJECT_BRIEF = A C-API implementation for Ikarus, a tool for worldbuilding. +PROJECT_NAME = LIBIKARUS +RECURSIVE = YES \ No newline at end of file diff --git a/docs/enum_format_fix.js b/docs/enum_format_fix.js new file mode 100644 index 0000000..2507ab8 --- /dev/null +++ b/docs/enum_format_fix.js @@ -0,0 +1,11 @@ +// maximum efficiency +function enumFormatFix() { + Array.from(document.getElementsByClassName("memItemRight")).forEach((elem) => { + elem.innerHTML = elem.innerHTML.replaceAll("
", ""); + elem.innerHTML = elem.innerHTML.replaceAll(" ", ""); + elem.innerHTML = elem.innerHTML.replaceAll("{", "{
    "); + elem.innerHTML = elem.innerHTML.replaceAll("\n,", ","); + elem.innerHTML = elem.innerHTML.replaceAll(",", ",
    "); + elem.innerHTML = elem.innerHTML.replaceAll("}", "
}"); + }); +} \ No newline at end of file diff --git a/docs/header.html b/docs/header.html new file mode 100644 index 0000000..dfce9a1 --- /dev/null +++ b/docs/header.html @@ -0,0 +1,97 @@ + + + + + + + + + $projectname: $title + $title + + + + + + + + + + + + + + + + + $treeview + $search + $mathjax + $darkmode + + $extrastylesheet + + + + +
+ + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
$projectname $projectnumber + +
+ +
$projectbrief
+
+
$projectbrief
+
$searchbox
$searchbox
+
+ + diff --git a/implementation_details b/implementation_details new file mode 100644 index 0000000..42b38fc --- /dev/null +++ b/implementation_details @@ -0,0 +1,12 @@ +This list is intended to help keep the documentation up to date. +If you make changes to, for example, templates, always check the documentation for templates. +But sometimes information is shared. and referenced in multiple places. This helps keep track of that. + +Usage: Search for these keys prefixed with IMPLEMENTATION_DETAIL_* to change documentation in relevant places. + +DATABASE: References to the usage of a database +TREE_LAYOUT: References to our usage of a tree layout +OBJECT_TYPES: References to the types of objects +OBJECT_SCOPES: References to the usage of object scopes +PROPERTY_TYPES: The property types that currently exist +LAZY_VALUE_CREATION: The fact that values are created lazily \ No newline at end of file diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt new file mode 100644 index 0000000..2e91ee9 --- /dev/null +++ b/include/CMakeLists.txt @@ -0,0 +1,8 @@ +file( + GLOB_RECURSE + FILES + "*.h" +) + +set(INCLUDE_FILES ${FILES} PARENT_SCOPE) + diff --git a/include/ikarus/macros.h b/include/ikarus/macros.h new file mode 100644 index 0000000..fdc6510 --- /dev/null +++ b/include/ikarus/macros.h @@ -0,0 +1,27 @@ +#pragma once + +#if defined(__unix__) + +#define IKA_OS_FAMILY_UNIX +#define IKA_API __attribute__((visibility("default"))) + +#if defined(linux) +#define IKA_OS_LINUX +#endif + +#elif defined(_WIN32) || defined(WIN32) +#define IKA_OS_WIN +#define IKA_API __declspec(dllexport) +#endif + +#ifndef IKA_API +#define IKA_API +#endif + +#ifdef __cplusplus +#define IKARUS_BEGIN_HEADER extern "C" { +#define IKARUS_END_HEADER } +#else +#define IKARUS_BEGIN_HEADER +#define IKARUS_END_HEADER +#endif diff --git a/include/ikarus/stdtypes.h b/include/ikarus/stdtypes.h new file mode 100644 index 0000000..2f467cf --- /dev/null +++ b/include/ikarus/stdtypes.h @@ -0,0 +1,8 @@ +#pragma once + +#ifdef __cplusplus +#include +using std::size_t; +#else +#include +#endif diff --git a/include/ikarus/types/id.h b/include/ikarus/types/id.h new file mode 100644 index 0000000..a205e43 --- /dev/null +++ b/include/ikarus/types/id.h @@ -0,0 +1,58 @@ +// IMPLEMENTATION_DETAIL_DATABASE + +/// \file id.h +/// \author Folling + +#pragma once + +#include + +IKARUS_BEGIN_HEADER + +#include +#include + +/// \defgroup id Ids +/// \brief Ids are used to identify objects in the database. +/// \details They are stored as 64 bit integers with the following layout: +/// - first 8 bits: #IkarusObjectType - 255 possible values, 0 for special values +/// - last 56 bits: incremented counter generated by the database +/// @{ + +/// \brief A wrapper around a 64 bit integer that represents the id of an object. +/// \details They are stored as 64 bit integers with the following layout: +/// - first 8 bits: #IkarusObjectType - 255 possible values, 0 for special values +/// - last 56 bits: incremented counter generated by the database +struct IkarusId { + /// \private \brief The value of the id. + uint64_t value; +}; + +/// \brief A special id returned by failed functions. +IkarusId const IKARUS_ID_NONE{0}; +/// \brief A special id used to indicate an optional id not being specified. +IkarusId const IKARUS_ID_UNSPECIFIED{1}; + +/// \private \brief Generates a new id for the given object type. +/// \param object_type The type of the object to generate an id for. +/// \return The generated id. +IkarusId ikarus_id_from_integer(IkarusObjectType object_type); + +/// \brief Fetches the object type of the given id. +/// \param id The id to fetch the object type for. +/// \return The object type of the given id. +IKA_API IkarusObjectType ikarus_id_get_object_type(IkarusId id); + +/// \brief Checks if the given id is IKARUS_ID_NONE. +/// \param id The id to check. +/// \return True if the id is IKARUS_ID_NONE, false otherwise. +IKA_API bool ikarus_id_is_none(IkarusId id); + +/// \brief Checks if the given id is IKARUS_ID_UNSPECIFIED. +/// \param id The id to check. +/// \return True if the id is IKARUS_ID_UNSPECIFIED, false otherwise. +IKA_API bool ikarus_id_is_unspecified(IkarusId id); + +/// @} + +IKARUS_END_HEADER \ No newline at end of file diff --git a/include/ikarus/types/object.h b/include/ikarus/types/object.h new file mode 100644 index 0000000..9d8b9bd --- /dev/null +++ b/include/ikarus/types/object.h @@ -0,0 +1,164 @@ +#pragma once + +// IMPLEMENTATION_DETAIL_OBJECT_TYPES +// IMPLEMENTATION_DETAIL_LAZY_VALUE_CREATION +// IMPLEMENTATION_DETAIL_PROPERTY_TYPES + +/// \file id.h +/// \author Folling + +/// \defgroup object Objects +/// \brief Objects are a compound type of all types of objects in the database. +/// \details The following objects currently exist: +/// - blueprints +/// - properties +/// - entities +/// - blueprint folders +/// - property folders +/// - entity folders +/// @{ + +#include + +IKARUS_BEGIN_HEADER + +/// \brief A blueprint object. +/// \details A blueprint is a collection of properties which can be linked to entities. +/// Each entity the blueprint is linked to will have values for the blueprints properties. +struct IkarusBlueprint { + IkarusId id; +}; + +/// \brief Properties are the placeholders of values for entities. +/// \details Each entity can have any number of properties. +/// Every property has a type that identifies the kind of data that can be put in. +/// +/// The following types currently exist: +/// - Toggle: A true/false boolean-like value +/// - Number: An arbitrary numeric value +/// - Text: An arbitrary textual value +/// +/// Property Examples: +/// - Is Dead (Toggle) +/// - Age (Number) +/// - ISBN (Text) +/// +/// Every property has settings which can be used to customise the property further. +/// Two settings that are shared among all properties are the following: +/// - Multiple +/// - Allow undefined +/// +/// The former transforms a property into a list. Instead of one number, you could then specify a series of numbers. +/// The latter allows you to specify an "unknown" value for a property. +/// It might not be known if a character is dead or not for example. +/// +/// Each entity associated with the property has a value for it. +/// +/// Properties can also be added to blueprints in which case they are available for all entities associated with the +/// blueprint. +/// +/// We call properties within entities "Entity Properties" and properties within blueprints "Blueprint Properties". +/// +/// \remark Values for properties are lazily created as space saving measure. +/// Fetching the value for some property of some entity will return the property's default value if none is specified. +/// This default value is specified when the property is created and can be updated later. +/// +/// \remark Properties' tree structures are scoped to the entity or blueprint they are associated with. +struct IkarusProperty { + /// \private \brief The ID of the property. + IkarusId id; +}; + +/// \brief Entities are the core building blocks of Ikarus. +/// \detials Blueprints and Properties define the structure of the data. +/// Entities define the data itself. +/// +/// Properties can be associated with Entities in two ways: +/// - Directly: The property is linked to the entity. +/// - Indirectly: The property is linked to a blueprint of the entity. +/// +/// For each property an entity is linked to, it has a value. These values depend on the property's type. +/// For more information on the types see the property documentation. +/// +/// Values are the core type of data within Ikarus. +/// Each value is associated with one page and one property. +/// +/// \remark Values are typed, the type of a value is specified by its associated property. +/// For more information on the types see the property documentation. +/// +/// \remark Values are guaranteed to be in valid format for a given type +/// but not guaranteed to be valid under the settings of the property. +/// This is because changing the settings can invalidate existing values without resetting them. +struct IkarusEntity { + /// \private \brief The ID of the entity. + IkarusId id; +}; + +/// \brief A blueprint folder. +/// \see Folder +struct IkarusBlueprintFolder { + /// \private \brief The ID of the folder. + IkarusId id; +}; + +/// \brief A property folder. +/// \remark Property folders are scoped to the blueprint or entity they are associated with. +/// \see Folder +struct IkarusPropertyFolder { + /// \private \brief The ID of the folder. + IkarusId id; +}; + +/// \brief An entity folder. +/// \see Folder +struct IkarusEntityFolder { + /// \private \brief The ID of the folder. + IkarusId id; +}; + +/// \private \brief The data of a folder. +union IkarusFolderData { + /// \private \brief The blueprint folder data of the folder. + IkarusBlueprintFolder blueprint_folder; + /// \private \brief The property folder data of the folder. + IkarusPropertyFolder property_folder; + /// \private \brief The entity folder data of the folder. + IkarusEntityFolder entity_folder; +}; + +/// \brief A generic folder. Similar to how Objects wrap all types of objects, Folders wrap all types of folders. +struct IkarusFolder { + /// \private \brief The data of the folder. + IkarusFolderData data; + + /// \private \brief The type of the folder. + IkarusFolderType type; +}; + +/// \private \brief The data of an object. +union IkarusObjectData { + /// \private \brief The blueprint data of the object. + IkarusBlueprint blueprint; + /// \private \brief The property data of the object. + IkarusProperty property; + /// \private \brief The entity data of the object. + IkarusEntity entity; + /// \private \brief The blueprint folder data of the object. + IkarusBlueprintFolder blueprint_folder; + /// \private \brief The property folder data of the object. + IkarusPropertyFolder property_folder; + /// \private \brief The entity folder data of the object. + IkarusEntityFolder entity_folder; +}; + +/// \brief A generic object. Wraps all types of objects, including folders. +struct IkarusObject { + /// \private \brief The data of the object. + IkarusObjectData data; + /// \private \brief The type of the object. + IkarusObjectType type; +}; + +// @} + +IKARUS_END_HEADER diff --git a/include/ikarus/types/object_scope.h b/include/ikarus/types/object_scope.h new file mode 100644 index 0000000..1fe51e1 --- /dev/null +++ b/include/ikarus/types/object_scope.h @@ -0,0 +1,149 @@ +// IMPLEMENTATION_DETAIL_OBJECT_SCOPES, IMPLEMENTATION_DETAIL_TREE_LAYOUT + +/// \file object_scope.h +/// \author Folling + +/// \defgroup object_scopes Object Scopes +/// \brief Scopes define where objects belong to. +/// \details They are required to differentiate between different types of objects with NULL as their parent. +/// @{ + +#pragma once + +#include +#include + +IKARUS_BEGIN_HEADER + +/// \brief The global scope of all blueprints. +struct IkarusBlueprintScope { + /// \private \brief Empty structs aren't allowed in C, so we need a dummy field. + short _dummy; +}; + +/// \brief Data for a property scope. This can either be a blueprint or an entity. +union IkarusPropertyScopeData { + /// \private \brief The blueprint the property is scoped to. + IkarusBlueprint _blueprint; + /// \private \brief The entity the property is scoped to. + IkarusEntity _entity; +}; + +/// \brief The type of a property scope. This can either be a blueprint or an entity. +enum IkarusPropertyScopeType { + /// \brief The property is scoped to a blueprint. + IkarusPropertyScopeType_Blueprint, + /// \brief The property is scoped to an entity. + IkarusPropertyScopeType_Entity +}; + +/// \brief The scope of a property +struct IkarusPropertyScope { + /// \private \brief Represents the type of the scope. + IkarusPropertyScopeType _type; + /// \private \brief Represents the data of the scope. + IkarusPropertyScopeData _data; +}; + +/// The global scope of all entities. +struct IkarusEntityScope { + /// \private \brief Empty structs aren't allowed in C, so we need a dummy field. + short _dummy; +}; + +/// \private \brief The data for an object scope. +union IkarusObjectScopeData { + /// \private \brief The blueprint data of the scope. + IkarusBlueprintScope _blueprint; + /// \private \brief The property data of the scope. + IkarusPropertyScope _property; + /// \private \brief The entity data of the scope. + IkarusEntityScope _entity; +}; + +/// The type of an object scope. +enum IkarusObjectScopeType { + /// \brief The scope is a blueprint scope. + IkarusObjectScopeType_Blueprint, + /// \brief The scope is a property scope. + IkarusObjectScopeType_Property, + /// \brief The scope is an entity scope. + IkarusObjectScopeType_Entity +}; + +/// \brief The scope of an object. +struct IkarusObjectScope { + /// \private \brief Represents the type of the scope. + IkarusObjectScopeType _type; + /// \private \brief Represents the data of the scope. + IkarusObjectScopeData _data; +}; + +/// \brief Creates a blueprint scope. +/// \return The created blueprint scope. +IKA_API IkarusBlueprintScope ikarus_blueprint_scope_create(); +/// \brief Converts a blueprint scope to an object scope. +/// \param scope The scope to convert. +/// \return The converted scope. +IKA_API IkarusObjectScope ikarus_blueprint_scope_to_object_scope(IkarusBlueprintScope const * scope); + +/// \brief Creates a property scope from a blueprint. +/// \param blueprint The blueprint the property is scoped to. +/// \return The created property scope. +IKA_API IkarusPropertyScope ikarus_property_scope_create_blueprint(IkarusBlueprint const * blueprint); +/// \brief Creates a property scope from a entity. +/// \param entity The entity the property is scoped to. +/// \return The created property scope. +IKA_API IkarusPropertyScope ikarus_property_scope_create_entity(IkarusEntity const * entity); +/// \brief Converts a property scope to an object scope. +/// \param scope The scope to convert. +/// \return The converted scope. +IKA_API IkarusObjectScope ikarus_property_scope_to_object_scope(IkarusPropertyScope const * scope); + +/// \brief Fetches the type of an property scope. +/// \param scope The scope to fetch the type of. +/// \return The type of the scope. +IKA_API IkarusPropertyScopeType ikarus_property_scope_get_type(IkarusPropertyScope const * scope); + +/// \brief Visits a property scope, calling the appropriate function. +/// \param scope The scope to to visit +/// \param blueprint The function to call if the property is scoped to a blueprint. +/// \param entity The function to call if the property is scoped to an entity. +/// \param data Optional data to pass to the functions. +void ikarus_property_scope_visit( + IkarusPropertyScope const * scope, + void (*blueprint)(IkarusBlueprint const *, void *), + void (*entity)(IkarusEntity const *, void *), + void * data +); + +/// \brief Creates an entity scope. +/// \return The created entity scope. +IKA_API IkarusEntityScope ikarus_entity_scope_create(); +/// Converts an entity scope to an object scope. +/// \param scope The scope to convert. +/// \return The converted scope. +IKA_API IkarusObjectScope ikarus_entity_scope_to_object_scope(IkarusEntityScope const * scope); + +/// \brief Fetches the type of an object scope. +/// \param scope The scope to fetch the type of. +/// \return The type of the scope. +IKA_API IkarusObjectScopeType ikarus_object_scope_get_type(IkarusObjectScope const * scope); + +/// \brief Visits an object scope, calling the appropriate function. +/// \param scope The scope to visit. +/// \param blueprint The function to call if the scope is an #IkarusBlueprintScope. +/// \param property The function to call if the scope is an #IkarusPropertyScope. +/// \param entity The function to call if the scope is an #IkarusEntityScope. +/// \remark function pointers may be null in which case they are not called. +IKA_API void ikarus_object_scope_visit( + IkarusObjectScope const * scope, + void (*blueprint)(IkarusBlueprintScope const *, void *), + void (*property)(IkarusPropertyScope const *, void *), + void (*entity)(IkarusEntityScope const *, void *), + void * data +); + +/// @} + +IKARUS_END_HEADER diff --git a/include/ikarus/types/object_type.h b/include/ikarus/types/object_type.h new file mode 100644 index 0000000..ca3dfb0 --- /dev/null +++ b/include/ikarus/types/object_type.h @@ -0,0 +1,63 @@ +#pragma once + +#include + +IKARUS_BEGIN_HEADER + +/// \defgroup object_types ObjectTypes +/// \brief ObjectTypes are used to identify the type of objects. +/// @{ + +/// \brief The type of a folder. +/// \remark folders have the first bit set and then mirror the object type of the underlying object +enum IkarusFolderType { + /// \brief Not a folder or no folder. + IkarusFolderType_None = 0, + /// \brief An IkarusBlueprintFolder + IkarusFolderType_BlueprintFolder = 0b1000'0001, + /// \brief An IkarusPropertyFolder + IkarusFolderType_PropertyFolder = 0b1000'0010, + /// \brief An IkarusEntityFolder + IkarusFolderType_EntityFolder = 0b1000'0011, +}; + +/// \brief The type of an object. +/// \remark folders have the first bit set and then mirror the object type of the underlying object +enum IkarusObjectType { + /// \brief Not an object or no object. + ObjectType_None = 0, + /// \brief An IkarusBlueprint. + ObjectType_Blueprint = 0b0000'0001, + /// \brief An IkarusProperty. + ObjectType_Property = 0b0000'0010, + /// \brief An IkarusEntity. + ObjectType_Entity = 0b0000'0011, + /// \brief An IkarusBlueprintFolder + ObjectType_BlueprintFolder = 0b1000'0001, + /// \brief An IkarusPropertyFolder + ObjectType_PropertyFolder = 0b1000'0010, + /// \brief An IkarusEntityFolder + ObjectType_EntityFolder = 0b1000'0011, +}; + +/// \brief A bitset of IkarusObjectType%s. +enum ObjectTypes { + /// \brief No object type. + ObjectTypes_None = 0, + /// \brief An IkarusBlueprint. + ObjectTypes_Blueprint = 1 << ObjectType_Blueprint, + /// \brief An IkarusProperty. + ObjectTypes_Property = 1 << ObjectType_Property, + /// \brief An IkarusEntity. + ObjectTypes_Entity = 1 << ObjectType_Entity, + /// \brief An IkarusBlueprintFolder + ObjectTypes_BlueprintFolder = 1 << ObjectType_BlueprintFolder, + /// \brief An IkarusPropertyFolder + ObjectTypes_PropertyFolder = 1 << ObjectType_PropertyFolder, + /// \brief An IkarusEntityFolder + ObjectTypes_EntityFolder = 1 << ObjectType_EntityFolder, +}; + +// @} + +IKARUS_END_HEADER \ No newline at end of file diff --git a/include/ikarus/types/property_type.h b/include/ikarus/types/property_type.h new file mode 100644 index 0000000..d22e1a6 --- /dev/null +++ b/include/ikarus/types/property_type.h @@ -0,0 +1,31 @@ +#pragma once + +// IMPLEMENTATION_DETAIL_PROPERTY_TYPES + +/// \file id.h +/// \author Folling + +#include + +IKARUS_BEGIN_HEADER + +/// \defgroup property_types Property Types +/// \brief Property Types delineate the type of data stored by a property. +/// @{ + +/// \brief The type of a property. +/// \details Designates the type of data stored by the property as well as which settings are +/// available. +/// \see IkarusPropertySettings +enum IkarusPropertyType { + /// \brief A true/false boolean-like value. + IkarusPropertyType_Toggle, + /// \brief An arbitrary numeric value. + IkarusPropertyType_Number, + /// \brief An arbitrary textual value. + IkarusPropertyType_Text, +}; + +/// @} + +IKARUS_END_HEADER diff --git a/include/ikarus/types/value.h b/include/ikarus/types/value.h new file mode 100644 index 0000000..d636d27 --- /dev/null +++ b/include/ikarus/types/value.h @@ -0,0 +1,161 @@ +#pragma once + +// IMPLEMENTATION_DETAIL_PROPERTY_TYPES + +#include +#include +#include + +IKARUS_BEGIN_HEADER + +/// \defgroup entity_value Entity Values +/// \brief The values stored in entities. +/// \details Each entity has a value for each property it is associated with. +/// The value is of the type specified by the property and constrained by the property's settings. +/// \see PropertyType PropertySettings +/// @{ + +/// \brief A true/false boolean-like value. For example "IsDead". +struct IkarusToggleValue { + /// \private \brief The value of the property. + bool _value; +}; + +/// \brief An arbitrary numeric value. For example "Age". +struct IkarusNumberValue { + /// \private \brief The value of the property. + long double _value; +}; + +/// \brief An arbitrary textual value. For example "First Name". +struct IkarusTextValue { + /// \private \brief The value of the property. + char const * _value; +}; + +/// \private \brief The data for a value. +union IkarusEntityValueData { + /// \private \brief The value as a toggle. + IkarusToggleValue toggle; + /// \private \brief The value as a number. + IkarusNumberValue number; + /// \private \brief The value as text. + IkarusTextValue text; +}; + +/// \brief The state of an entity value. +/// \details States provide insight into the nature of a value. +enum IkarusEntityValueState { + /// \brief The value is invalid. + IkarusEntityValueState_Invalid, + /// \brief The value is normal and can be used as-is. + IkarusEntityValueState_Normal, + /// \brief The value is unknown. + IkarusEntityValueState_Indeterminate, +}; + +/// \brief The value of an entity associated with a property. +struct IkarusEntityValue { + /// \private \brief The type of the value. + IkarusPropertyType _type; + /// \private \brief The data for the value.p + IkarusEntityValueData _data; + /// \private \brief The state of the value. + IkarusEntityValueState _state; +}; + +/// \brief Creates an entity value from a toggle value. +/// \param value The toggle value. +/// \return The entity value. +IKA_API IkarusEntityValue ikarus_value_create_toggle(bool value); +/// \brief Creates an entity value from a number value. +/// \param value The number value. +/// \return The entity value. +/// \remark If the value is NaN or infinity an InvalidEntityValue is returned. +IKA_API IkarusEntityValue ikarus_value_create_number(long double value); +/// \brief Creates an entity value from a text value. +/// \param value The text value. +/// \return The entity value. +/// \remark If the value is null an InvalidEntityValue is returned. +IKA_API IkarusEntityValue ikarus_value_create_text(char const * value); + +/// \brief Creates an indeterminate entity value of a given type. +/// \param type The type of the value. +/// \return The entity value. +IKA_API IkarusEntityValue ikarus_value_create_indeterminate(IkarusPropertyType type); + +/// \brief Fetches the default value for a property type. +/// \remark Not to be confused with the default value of a property. See ikarus_property_get_default_value +/// \param type The property type. +/// \return The default value for the property type. +IKA_API IkarusEntityValue ikarus_value_get_default(IkarusPropertyType type); + +/// \brief Fetches the underlying value of a toggle value. +/// \param value The toggle value. +/// \return The underlying value. +IKA_API bool ikarus_toggle_value_get_underlying(IkarusToggleValue const * value); + +/// \brief Fetches the underlying value of a number value. +/// \param value The number value. +/// \return The underlying value. +IKA_API long double ikarus_number_value_get_underlying(IkarusNumberValue const * value); + +/// \brief Fetches the underlying value of a text value. +/// \param value The text value. +/// \return The underlying value. +IKA_API char const * ikarus_text_value_get_underlying(IkarusTextValue const * value); + +/// \brief Checks if a toggle value is equal to a boolean. +/// \param value The toggle value. +/// \param check The boolean value. +/// \return False if value is null. True if it is equal to check, false otherwise. +IKA_API bool ikarus_toggle_value_is_equal(IkarusToggleValue const * value, bool check); + +/// \brief Checks if a number value is equal to a number. +/// \param value The number value. +/// \param check The number value. +/// \return False if value is null. True if it is equal to check, false otherwise. +IKA_API bool ikarus_number_value_is_equal(IkarusNumberValue const * value, long double check); + +/// \brief Checks if a text value is equal to a string. +/// \param value The text value. +/// \param check The string value. +/// \return False if value or check are null. True if it is equal to check, false otherwise. +IKA_API bool ikarus_text_value_is_equal(IkarusTextValue const * value, char const * check); + +/// \brief Checks if two entity values are equal. +/// \details Two entity values are equal if they are of the same type and their value is considered equal. +/// Note that floating point values can only be checked for approximate equality. +/// \param left The left-hand entity value. +/// \param right The right-hand entity value. +/// \return True if the values are considered equal, false otherwise. +/// \remark Null values compare false to all other values. As do invalid values. Indeterminate values however, compare true to +/// other indeterminate values of the same type. +IKA_API bool ikarus_value_is_equal(IkarusEntityValue const * left, IkarusEntityValue const * right); + +/// \brief Checks if an entity value is invalid. +/// \param value The entity value. +/// \return True if the value is invalid or null, false otherwise. +IKA_API bool ikarus_value_is_invalid(IkarusEntityValue const * value); + +/// \brief Fetches the type of an entity value. +/// \param value The entity value. +/// \return The type of the entity value. +IKA_API IkarusPropertyType ikarus_value_get_type(IkarusEntityValue const * value); + +/// \brief Visits an entity value, calling the appropriate function for the value's type. +/// \param value The entity value to visit. +/// \param toggle The function to call if the value is a toggle value. +/// \param number The function to call if the value is a number value. +/// \param text The function to call if the value is a text value. +/// \param data The data to pass to the functions. +/// \remark function pointers may be null in which case they are not called. +IKA_API void ikarus_value_visit( + IkarusEntityValue const * value, + void (*toggle)(IkarusToggleValue const *, void *), + void (*number)(IkarusNumberValue const *, void *), + void (*text)(IkarusTextValue const *, void *), + void * data +); + +IKARUS_END_HEADER diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..a0665aa --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,7 @@ +file( + GLOB_RECURSE + FILES + "*.cpp" +) + +set(SOURCE_FILES ${FILES} PARENT_SCOPE) \ No newline at end of file diff --git a/src/types/object_scope.cpp b/src/types/object_scope.cpp new file mode 100644 index 0000000..a4f2b49 --- /dev/null +++ b/src/types/object_scope.cpp @@ -0,0 +1,209 @@ +#include "ikarus/types/object_scope.h" + +#include + +#include + +IkarusBlueprintScope ikarus_blueprint_scope_create() { + return IkarusBlueprintScope{._dummy = 0}; +} + +IkarusObjectScope ikarus_blueprint_scope_to_object_scope(IkarusBlueprintScope const * scope) { + IkarusObjectScopeData data{}; + data._blueprint = *scope; + + return IkarusObjectScope{._type = IkarusObjectScopeType_Blueprint, ._data = data}; +} + +IkarusPropertyScope ikarus_property_scope_create_blueprint(IkarusBlueprint const * blueprint) { + IkarusPropertyScopeData data{}; + data._blueprint = *blueprint; + return IkarusPropertyScope{._type = IkarusPropertyScopeType_Blueprint, ._data = data}; +} + +IkarusPropertyScope ikarus_property_scope_create_entity(IkarusEntity const * entity) { + IkarusPropertyScopeData data{}; + data._entity = *entity; + return IkarusPropertyScope{._type = IkarusPropertyScopeType_Entity, ._data = data}; +} + +IkarusObjectScope ikarus_property_scope_to_object_scope(IkarusPropertyScope const * scope) { + IkarusObjectScopeData data{}; + data._property = *scope; + + return IkarusObjectScope{._type = IkarusObjectScopeType_Property, ._data = data}; +} + +IkarusPropertyScopeType ikarus_property_scope_get_type(IkarusPropertyScope const * scope) { + return scope->_type; +} + +void ikarus_property_scope_visit( + IkarusPropertyScope const * scope, + void (*blueprint)(IkarusBlueprint const *, void *), + void (*entity)(IkarusEntity const *, void *), + void * data +) { + switch (scope->_type) { + case IkarusPropertyScopeType_Blueprint: blueprint(&scope->_data._blueprint, data); break; + case IkarusPropertyScopeType_Entity: entity(&scope->_data._entity, data); break; + } +} + +IkarusEntityScope ikarus_entity_scope_create() { + return IkarusEntityScope{._dummy = 0}; +} + +IkarusObjectScope ikarus_entity_scope_to_object_scope(IkarusEntityScope const * scope) { + IkarusObjectScopeData data{}; + data._entity = *scope; + + return IkarusObjectScope{._type = IkarusObjectScopeType_Entity, ._data = data}; +} + +IkarusObjectScopeType ikarus_object_scope_get_type(IkarusObjectScope const * scope) { + return scope->_type; +} + +void ikarus_object_scope_visit( + IkarusObjectScope const * scope, + void (*blueprint)(IkarusBlueprintScope const *, void *), + void (*property)(IkarusPropertyScope const *, void *), + void (*entity)(IkarusEntityScope const *, void *), + void * data +) { + switch (scope->_type) { + case IkarusObjectScopeType_Blueprint: { + if (blueprint != nullptr) { + blueprint(&scope->_data._blueprint, data); + } + break; + } + case IkarusObjectScopeType_Property: { + if (property != nullptr) { + property(&scope->_data._property, data); + } + break; + } + case IkarusObjectScopeType_Entity: { + if (entity != nullptr) { + entity(&scope->_data._entity, data); + } + break; + } + } +} + +TEST_CASE("blueprint_object_scope_conversion", "[object_scope]") { + auto blueprint_scope = ikarus_blueprint_scope_create(); + auto blueprint_object_scope = ikarus_blueprint_scope_to_object_scope(&blueprint_scope); + REQUIRE(blueprint_object_scope._type == IkarusObjectScopeType_Blueprint); +} + +TEST_CASE("property_scope_type", "[object_scope]") { + auto blueprint = IkarusBlueprint{}; + auto entity = IkarusEntity{}; + + auto property_blueprint_scope = ikarus_property_scope_create_blueprint(&blueprint); + auto property_entity_scope = ikarus_property_scope_create_entity(&entity); + + REQUIRE(ikarus_property_scope_get_type(&property_blueprint_scope) == IkarusPropertyScopeType_Blueprint); + REQUIRE(ikarus_property_scope_get_type(&property_entity_scope) == IkarusPropertyScopeType_Entity); +} + +TEST_CASE("property_object_scope_conversion", "[object_scope]") { + auto blueprint = IkarusBlueprint{}; + auto entity = IkarusEntity{}; + + auto property_blueprint_scope = ikarus_property_scope_create_blueprint(&blueprint); + auto property_blueprint_object_scope = ikarus_property_scope_to_object_scope(&property_blueprint_scope); + + REQUIRE(property_blueprint_object_scope._type == IkarusObjectScopeType_Property); + + auto property_entity_scope = ikarus_property_scope_create_entity(&entity); + auto property_entity_object_scope = ikarus_property_scope_to_object_scope(&property_entity_scope); + + REQUIRE(property_entity_object_scope._type == IkarusObjectScopeType_Property); +} + +TEST_CASE("property_scope_visiting", "[object_scope]") { + auto blueprint = IkarusBlueprint{}; + auto entity = IkarusEntity{}; + + auto property_blueprint_scope = ikarus_property_scope_create_blueprint(&blueprint); + auto property_entity_scope = ikarus_property_scope_create_entity(&entity); + + int test = 0; + + ikarus_property_scope_visit( + &property_blueprint_scope, + [](IkarusBlueprint const * _, void * data) { *reinterpret_cast(data) = 1; }, + [](IkarusEntity const * _, void * data) { *reinterpret_cast(data) = 2; }, + &test + ); + + REQUIRE(test == 1); + + ikarus_property_scope_visit( + &property_entity_scope, + [](IkarusBlueprint const * _, void * data) { *reinterpret_cast(data) = 1; }, + [](IkarusEntity const * _, void * data) { *reinterpret_cast(data) = 2; }, + &test + ); + + REQUIRE(test == 2); +} + +TEST_CASE("entity_object_scope_conversion", "[object_scope]") { + auto entity_scope = ikarus_entity_scope_create(); + auto entity_object_scope = ikarus_entity_scope_to_object_scope(&entity_scope); + REQUIRE(entity_object_scope._type == IkarusObjectScopeType_Entity); +} + +TEST_CASE("object_scope_type_fetching", "[object_scope]") { + auto blueprint = IkarusBlueprint{}; + + auto blueprint_scope = ikarus_blueprint_scope_create(); + auto property_scope = ikarus_property_scope_create_blueprint(&blueprint); + auto entity_scope = ikarus_entity_scope_create(); + + auto blueprint_object_scope = ikarus_blueprint_scope_to_object_scope(&blueprint_scope); + auto property_object_scope = ikarus_property_scope_to_object_scope(&property_scope); + auto entity_object_scope = ikarus_entity_scope_to_object_scope(&entity_scope); + + REQUIRE(ikarus_object_scope_get_type(&blueprint_object_scope) == IkarusObjectScopeType_Blueprint); + REQUIRE(ikarus_object_scope_get_type(&property_object_scope) == IkarusObjectScopeType_Property); + REQUIRE(ikarus_object_scope_get_type(&entity_object_scope) == IkarusObjectScopeType_Entity); +} + +TEST_CASE("object_scope_visiting", "[object_scope]") { + auto blueprint = IkarusBlueprint{}; + + auto blueprint_scope = ikarus_blueprint_scope_create(); + auto property_scope = ikarus_property_scope_create_blueprint(&blueprint); + auto entity_scope = ikarus_entity_scope_create(); + + auto blueprint_object_scope = ikarus_blueprint_scope_to_object_scope(&blueprint_scope); + auto property_object_scope = ikarus_property_scope_to_object_scope(&property_scope); + auto entity_object_scope = ikarus_entity_scope_to_object_scope(&entity_scope); + + auto scopes = { + std::make_pair(blueprint_object_scope, 1), + std::make_pair(property_object_scope, 2), + std::make_pair(entity_object_scope, 3), + }; + + for (auto [scope, value] : scopes) { + int test = 0; + + ikarus_object_scope_visit( + &scope, + [](IkarusBlueprintScope const * _, void * data) { *reinterpret_cast(data) = 1; }, + [](IkarusPropertyScope const * _, void * data) { *reinterpret_cast(data) = 2; }, + [](IkarusEntityScope const * _, void * data) { *reinterpret_cast(data) = 3; }, + &test + ); + + REQUIRE(test == value); + } +} diff --git a/src/types/value.cpp b/src/types/value.cpp new file mode 100644 index 0000000..251db2c --- /dev/null +++ b/src/types/value.cpp @@ -0,0 +1,337 @@ +#include "ikarus/types/value.h" + +#include +#include + +#include + +/// \brief Creates an indeterminate entity value of a given type. +/// \param type The type of the value. +/// \return The entity value. +IKA_API IkarusEntityValue value_create_invalid(IkarusPropertyType type) { + return IkarusEntityValue{ + ._type = type, + ._data = IkarusEntityValueData{}, + ._state = IkarusEntityValueState_Invalid, + }; +} + +IkarusEntityValue ikarus_value_create_toggle(bool value) { + return IkarusEntityValue{ + ._type = IkarusPropertyType_Toggle, + ._data = IkarusEntityValueData{.toggle = IkarusToggleValue{._value = value}}, + ._state = IkarusEntityValueState_Normal, + }; +} + +IkarusEntityValue ikarus_value_create_number(long double value) { + if (auto fp_class = std::fpclassify(value); fp_class != FP_NORMAL && fp_class != FP_ZERO) { + return value_create_invalid(IkarusPropertyType_Number); + } + + return IkarusEntityValue{ + ._type = IkarusPropertyType_Number, + ._data = IkarusEntityValueData{.number = IkarusNumberValue{._value = value}}, + ._state = IkarusEntityValueState_Normal, + }; +} + +IkarusEntityValue ikarus_value_create_text(char const * value) { + if (value == nullptr) { + return value_create_invalid(IkarusPropertyType_Text); + }; + + return IkarusEntityValue{ + ._type = IkarusPropertyType_Text, + ._data = IkarusEntityValueData{.text = IkarusTextValue{._value = value}}, + ._state = IkarusEntityValueState_Normal, + }; +} + +IkarusEntityValue ikarus_value_create_indeterminate(IkarusPropertyType type) { + IkarusEntityValueData data{}; + + switch (type) { + case IkarusPropertyType_Toggle: { + data.toggle = IkarusToggleValue{._value = false}; + break; + } + case IkarusPropertyType_Number: { + data.number = IkarusNumberValue{._value = 0.0}; + break; + } + case IkarusPropertyType_Text: { + data.text = IkarusTextValue{._value = ""}; + break; + } + default: return value_create_invalid(type); + } + + return IkarusEntityValue{ + ._type = type, + ._data = data, + ._state = IkarusEntityValueState_Indeterminate, + }; +} + +IkarusEntityValue ikarus_value_get_default(IkarusPropertyType type) { + switch (type) { + case IkarusPropertyType_Toggle: return ikarus_value_create_toggle(false); + case IkarusPropertyType_Number: return ikarus_value_create_number(0.0); + case IkarusPropertyType_Text: return ikarus_value_create_text(""); + default: return value_create_invalid(type); + } +} + +bool ikarus_toggle_value_get_underlying(IkarusToggleValue const * value) { + return value->_value; +} + +long double ikarus_number_value_get_underlying(IkarusNumberValue const * value) { + return value->_value; +} + +char const * ikarus_text_value_get_underlying(IkarusTextValue const * value) { + return value->_value; +} + +// no need to check for validity here, since these concrete types are only created by the library +bool ikarus_toggle_value_is_equal(IkarusToggleValue const * value, bool check) { + return value != nullptr && value->_value == check; +} + +bool ikarus_number_value_is_equal(IkarusNumberValue const * value, long double check) { + return value != nullptr && value->_value == check; +} + +bool ikarus_text_value_is_equal(IkarusTextValue const * value, char const * check) { + return value != nullptr && check != nullptr && std::strcmp(value->_value, check) == 0; +} + +bool ikarus_value_is_equal(IkarusEntityValue const * left, IkarusEntityValue const * right) { + if (left == nullptr || right == nullptr) { + return false; + } + + if (left->_state == IkarusEntityValueState_Invalid || right->_state == IkarusEntityValueState_Invalid) { + return false; + } + + if (left->_type != right->_type) { + return false; + } + + // indeterminate values are only equal if they have the same type + if (left->_state == IkarusEntityValueState_Indeterminate && right->_state == IkarusEntityValueState_Indeterminate) { + return true; + } + + switch (left->_type) { + case IkarusPropertyType_Toggle: return left->_data.toggle._value == right->_data.toggle._value; + case IkarusPropertyType_Number: return left->_data.number._value == right->_data.number._value; + case IkarusPropertyType_Text: return std::strcmp(left->_data.text._value, right->_data.text._value) == 0; + default: return false; + } +} + +bool ikarus_value_is_invalid(IkarusEntityValue const * value) { + return value == nullptr || value->_state == IkarusEntityValueState_Invalid; +} + +IkarusPropertyType ikarus_value_get_type(IkarusEntityValue const * value) { + return value->_type; +} + +void ikarus_value_visit( + IkarusEntityValue const * value, + void (*toggle)(IkarusToggleValue const * value, void * data), + void (*number)(IkarusNumberValue const * value, void * data), + void (*text)(IkarusTextValue const * value, void * data), + void * data +) { + if (value == nullptr) { + return; + } + + switch (value->_type) { + case IkarusPropertyType_Toggle: { + if (toggle != nullptr) { + toggle(&value->_data.toggle, data); + } + break; + } + case IkarusPropertyType_Number: { + if (number != nullptr) { + number(&value->_data.number, data); + } + break; + } + case IkarusPropertyType_Text: { + if (text != nullptr) { + text(&value->_data.text, data); + } + break; + } + default: break; + } +} + +TEST_CASE("toggle_value_creation", "[value]") { + auto toggle_value = ikarus_value_create_toggle(true); + + REQUIRE(ikarus_value_get_type(&toggle_value) == IkarusPropertyType_Toggle); + REQUIRE(ikarus_toggle_value_is_equal(&toggle_value._data.toggle, true)); +} + +TEST_CASE("number_value_creation", "[value]") { + auto number_value = ikarus_value_create_number(1.0); + + REQUIRE(ikarus_value_get_type(&number_value) == IkarusPropertyType_Number); + REQUIRE(ikarus_number_value_is_equal(&number_value._data.number, 1.0)); + + auto nan_value = ikarus_value_create_number(std::numeric_limits::quiet_NaN()); + REQUIRE(ikarus_value_is_invalid(&nan_value)); + auto signaling_non_value = ikarus_value_create_number(std::numeric_limits::signaling_NaN()); + REQUIRE(ikarus_value_is_invalid(&signaling_non_value)); + auto inf_value = ikarus_value_create_number(std::numeric_limits::infinity()); + REQUIRE(ikarus_value_is_invalid(&inf_value)); + auto neg_inf_value = ikarus_value_create_number(-std::numeric_limits::infinity()); + REQUIRE(ikarus_value_is_invalid(&neg_inf_value)); +} + +TEST_CASE("text_value_creation", "[value]") { + auto text_value = ikarus_value_create_text("test"); + + REQUIRE(ikarus_value_get_type(&text_value) == IkarusPropertyType_Text); + REQUIRE(ikarus_text_value_is_equal(&text_value._data.text, "test")); + + auto null_value = ikarus_value_create_text(nullptr); + REQUIRE(ikarus_value_is_invalid(&null_value)); +} + +TEST_CASE("default_value_creation", "[value]") { + auto types = { + IkarusPropertyType_Toggle, + IkarusPropertyType_Number, + IkarusPropertyType_Text, + }; + + for (auto type : types) { + auto value = ikarus_value_get_default(type); + REQUIRE(ikarus_value_get_type(&value) == type); + } +} + +TEST_CASE("toggle_value_underlying", "[value]") { + auto true_toggle_value = ikarus_value_create_toggle(true); + auto false_toggle_value = ikarus_value_create_toggle(false); + + REQUIRE(ikarus_toggle_value_get_underlying(&true_toggle_value._data.toggle) == true); + REQUIRE(ikarus_toggle_value_get_underlying(&false_toggle_value._data.toggle) == false); +} + +TEST_CASE("number_value_underlying", "[value]") { + auto zero_number_value = ikarus_value_create_number(0.0); + auto third_number_value = ikarus_value_create_number(1.0 / 3.0); + auto large_number_value = ikarus_value_create_number(1.2345678910e123); + + REQUIRE(ikarus_number_value_get_underlying(&zero_number_value._data.number) == 0.0); + REQUIRE(ikarus_number_value_get_underlying(&third_number_value._data.number) == 1.0 / 3.0); + REQUIRE(ikarus_number_value_get_underlying(&large_number_value._data.number) == 1.2345678910e123); +} + +TEST_CASE("text_value_underlying", "[value]") { + auto test_value = ikarus_value_create_text("test"); + auto empty_value = ikarus_value_create_text(""); + + REQUIRE(std::strcmp(ikarus_text_value_get_underlying(&test_value._data.text), "test") == 0); + REQUIRE(std::strcmp(ikarus_text_value_get_underlying(&empty_value._data.text), "") == 0); +} + +TEST_CASE("toggle_comparison", "[value]") { + auto true_toggle_value = ikarus_value_create_toggle(true); + auto false_toggle_value = ikarus_value_create_toggle(false); + + REQUIRE(ikarus_toggle_value_is_equal(&true_toggle_value._data.toggle, true)); + REQUIRE(ikarus_toggle_value_is_equal(&false_toggle_value._data.toggle, false)); +} + +TEST_CASE("number_comparison", "[value]") { + auto zero_number_value = ikarus_value_create_number(0.0); + auto third_number_value = ikarus_value_create_number(1.0 / 3.0); + auto large_number_value = ikarus_value_create_number(1.2345678910e123); + + REQUIRE(ikarus_number_value_is_equal(&zero_number_value._data.number, 0.0)); + REQUIRE(ikarus_number_value_is_equal(&third_number_value._data.number, 1.0 / 6.0 + 1.0 / 6.0)); + REQUIRE(ikarus_number_value_is_equal(&large_number_value._data.number, 1.2345678910e123)); +} + +TEST_CASE("text_comparison", "[value]") { + auto test_value = ikarus_value_create_text("test"); + auto empty_value = ikarus_value_create_text(""); + + REQUIRE(ikarus_text_value_is_equal(&test_value._data.text, "test")); + REQUIRE(ikarus_text_value_is_equal(&empty_value._data.text, "")); +} + +TEST_CASE("value_comparison", "[value]") { + auto true_toggle_value = ikarus_value_create_toggle(true); + auto false_toggle_value = ikarus_value_create_toggle(false); + auto number_value1 = ikarus_value_create_number(0.0); + auto number_value2 = ikarus_value_create_number(0.0); + auto invalid_value = ikarus_value_create_text(nullptr); + + auto indeterminate_toggle = ikarus_value_create_indeterminate(IkarusPropertyType_Toggle); + auto indeterminate_number1 = ikarus_value_create_indeterminate(IkarusPropertyType_Number); + auto indeterminate_number2 = ikarus_value_create_indeterminate(IkarusPropertyType_Number); + + REQUIRE(!ikarus_value_is_equal(nullptr, nullptr)); + REQUIRE(!ikarus_value_is_equal(&true_toggle_value, nullptr)); + REQUIRE(!ikarus_value_is_equal(nullptr, &true_toggle_value)); + + REQUIRE(!ikarus_value_is_equal(&invalid_value, &invalid_value)); + REQUIRE(!ikarus_value_is_equal(&true_toggle_value, &invalid_value)); + REQUIRE(!ikarus_value_is_equal(&invalid_value, &true_toggle_value)); + + REQUIRE(ikarus_value_is_equal(&true_toggle_value, &true_toggle_value)); + REQUIRE(!ikarus_value_is_equal(&true_toggle_value, &false_toggle_value)); + REQUIRE(!ikarus_value_is_equal(&true_toggle_value, &number_value1)); + REQUIRE(!ikarus_value_is_equal(&number_value1, &true_toggle_value)); + REQUIRE(ikarus_value_is_equal(&number_value1, &number_value2)); + + REQUIRE(!ikarus_value_is_equal(&indeterminate_toggle, &indeterminate_number1)); + REQUIRE(ikarus_value_is_equal(&indeterminate_number1, &indeterminate_number2)); +} + +TEST_CASE("invalid_value", "[value]") { + auto invalid_value = ikarus_value_create_toggle(false); + invalid_value._state = IkarusEntityValueState_Invalid; + + REQUIRE(ikarus_value_is_invalid(&invalid_value)); +} + +TEST_CASE("visit_value", "[value]") { + auto toggle_value = ikarus_value_create_toggle(true); + auto number_value = ikarus_value_create_number(0.0); + auto text_value = ikarus_value_create_text("test"); + + auto values = { + std::make_pair(toggle_value, 1), + std::make_pair(number_value, 2), + std::make_pair(text_value, 3), + }; + + for (auto [value, expected] : values) { + int test = 0; + + ikarus_value_visit( + &value, + [](IkarusToggleValue const * _, void * data) { *reinterpret_cast(data) = 1; }, + [](IkarusNumberValue const * _, void * data) { *reinterpret_cast(data) = 2; }, + [](IkarusTextValue const * _, void * data) { *reinterpret_cast(data) = 3; }, + &test + ); + + REQUIRE(test == expected); + } +} diff --git a/vendor/CMakeLists.txt b/vendor/CMakeLists.txt new file mode 100644 index 0000000..9820477 --- /dev/null +++ b/vendor/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(catch2) +add_subdirectory(sqlitecpp) diff --git a/vendor/catch2 b/vendor/catch2 new file mode 160000 index 0000000..5bba3e4 --- /dev/null +++ b/vendor/catch2 @@ -0,0 +1 @@ +Subproject commit 5bba3e4038602badb691da914523f667a2dd1f27 diff --git a/vendor/doxygen-awesome-css b/vendor/doxygen-awesome-css new file mode 160000 index 0000000..00a52f6 --- /dev/null +++ b/vendor/doxygen-awesome-css @@ -0,0 +1 @@ +Subproject commit 00a52f6c74065ffbd836cbd791ddfe8edf2836b8 diff --git a/vendor/sqlitecpp b/vendor/sqlitecpp new file mode 160000 index 0000000..afe7b16 --- /dev/null +++ b/vendor/sqlitecpp @@ -0,0 +1 @@ +Subproject commit afe7b165002ccf86a37da5b6b157ce4ff9db0401