a new beginning

Signed-off-by: Folling <mail@folling.io>
This commit is contained in:
folling 2023-08-22 00:27:22 +02:00 committed by Folling
commit dff8d96c55
Signed by: folling
SSH key fingerprint: SHA256:S9qEx5WCFFLK49tE/LKnKuJYM5sw+++Dn6qJbbyxnCY
25 changed files with 1718 additions and 0 deletions

222
.clang-format Normal file
View file

@ -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
# <string.h>
- Regex: '^<[a-z0-9_]+\.h>$'
Priority: 2
# C++ Includes
# <string>
- Regex: '^<[a-z0-9_]+>$'
Priority: 3
# expected
# <expected/ranges.hpp>
- Regex: '^<expected/.*>$'
Priority: 4
# libfmt
# <fmt/core.hpp>
- Regex: '^<fmt/.*>$'
Priority: 5
# nlohmann::json
# <nlohmann/json.hpp>
- Regex: '^<nlohmann/.*>$'
Priority: 6
# ranges
# <range-v3/ranges.hpp>
- Regex: '^<range-v3/.*>$'
Priority: 7
# ICU
# <unicode/ranges.hpp>
- Regex: '^<unicode/.*>$'
Priority: 8
# ranges
# <range-v3/ranges.hpp>
- Regex: '^<cppbase/.*>$'
Priority: 9
# ranges
# <range-v3/ranges.hpp>
- Regex: '^<sqlitecpp/.*>$'
Priority: 10
# ranges
# <range-v3/ranges.hpp>
- Regex: '^<ikarus/.*>$'
Priority: 11
# ranges
# <range-v3/ranges.hpp>
- Regex: '^<generated/.*>$'
Priority: 12
# ranges
# ranges
# <range-v3/ranges.hpp>
- Regex: '^<impl/.*>$'
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

58
.gitignore vendored Normal file
View file

@ -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

9
.gitmodules vendored Normal file
View file

@ -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

55
CMakeLists.txt Normal file
View file

@ -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)

11
README.md Normal file
View file

@ -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.

16
docs/DoxyFile Normal file
View file

@ -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

11
docs/enum_format_fix.js Normal file
View file

@ -0,0 +1,11 @@
// maximum efficiency
function enumFormatFix() {
Array.from(document.getElementsByClassName("memItemRight")).forEach((elem) => {
elem.innerHTML = elem.innerHTML.replaceAll("<br>", "");
elem.innerHTML = elem.innerHTML.replaceAll("&nbsp;", "");
elem.innerHTML = elem.innerHTML.replaceAll("{", "{<br>&nbsp;&nbsp;&nbsp;&nbsp;");
elem.innerHTML = elem.innerHTML.replaceAll("\n,", ",");
elem.innerHTML = elem.innerHTML.replaceAll(",", ",<br>&nbsp;&nbsp;&nbsp;&nbsp;");
elem.innerHTML = elem.innerHTML.replaceAll("}", "<br>}");
});
}

97
docs/header.html Normal file
View file

@ -0,0 +1,97 @@
<!-- HTML header for doxygen 1.9.7-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="$langISO">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=11"/>
<meta name="generator" content="Doxygen $doxygenversion"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN FULL_SIDEBAR-->
<script type="text/javascript">var page_layout = 1;</script>
<!--END FULL_SIDEBAR-->
<!--END DISABLE_INDEX-->
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
<script type="text/javascript" src="$relpath^doxygen-awesome-darkmode-toggle.js"></script>
<script type="text/javascript">
DoxygenAwesomeDarkModeToggle.init()
</script>
<script type="text/javascript" src="$relpath^doxygen-awesome-fragment-copy-button.js"></script>
<script type="text/javascript">
DoxygenAwesomeFragmentCopyButton.init()
</script>
<script type="text/javascript" src="$relpath^doxygen-awesome-paragraph-link.js"></script>
<script type="text/javascript">
DoxygenAwesomeParagraphLink.init()
</script>
<script type="text/javascript" src="$relpath^enum_format_fix.js"></script>
<script type="text/javascript">
window.onload = function () {
enumFormatFix()
}
</script>
$treeview
$search
$mathjax
$darkmode
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css"/>
$extrastylesheet
</head>
<body>
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN FULL_SIDEBAR-->
<div id="side-nav" class="ui-resizable side-nav-resizable"><!-- do not remove this div, it is closed by doxygen! -->
<!--END FULL_SIDEBAR-->
<!--END DISABLE_INDEX-->
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr id="projectrow">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"/></td>
<!--END PROJECT_LOGO-->
<!--BEGIN PROJECT_NAME-->
<td id="projectalign">
<div id="projectname">$projectname<!--BEGIN PROJECT_NUMBER--><span id="projectnumber">&#160;$projectnumber</span>
<!--END PROJECT_NUMBER-->
</div>
<!--BEGIN PROJECT_BRIEF-->
<div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
</td>
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<!--BEGIN PROJECT_BRIEF-->
<td>
<div id="projectbrief">$projectbrief</div>
</td>
<!--END PROJECT_BRIEF-->
<!--END !PROJECT_NAME-->
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<!--BEGIN !FULL_SIDEBAR-->
<td>$searchbox</td>
<!--END !FULL_SIDEBAR-->
<!--END SEARCHENGINE-->
<!--END DISABLE_INDEX-->
</tr>
<!--BEGIN SEARCHENGINE-->
<!--BEGIN FULL_SIDEBAR-->
<tr>
<td colspan="2">$searchbox</td>
</tr>
<!--END FULL_SIDEBAR-->
<!--END SEARCHENGINE-->
</tbody>
</table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->

12
implementation_details Normal file
View file

@ -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

8
include/CMakeLists.txt Normal file
View file

@ -0,0 +1,8 @@
file(
GLOB_RECURSE
FILES
"*.h"
)
set(INCLUDE_FILES ${FILES} PARENT_SCOPE)

27
include/ikarus/macros.h Normal file
View file

@ -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

View file

@ -0,0 +1,8 @@
#pragma once
#ifdef __cplusplus
#include <cstdint>
using std::size_t;
#else
#include <stdint.h>
#endif

58
include/ikarus/types/id.h Normal file
View file

@ -0,0 +1,58 @@
// IMPLEMENTATION_DETAIL_DATABASE
/// \file id.h
/// \author Folling <mail@folling.io>
#pragma once
#include <ikarus/macros.h>
IKARUS_BEGIN_HEADER
#include <ikarus/stdtypes.h>
#include <ikarus/types/object_type.h>
/// \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

View file

@ -0,0 +1,164 @@
#pragma once
// IMPLEMENTATION_DETAIL_OBJECT_TYPES
// IMPLEMENTATION_DETAIL_LAZY_VALUE_CREATION
// IMPLEMENTATION_DETAIL_PROPERTY_TYPES
/// \file id.h
/// \author Folling <mail@folling.io>
/// \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/types/id.h>
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

View file

@ -0,0 +1,149 @@
// IMPLEMENTATION_DETAIL_OBJECT_SCOPES, IMPLEMENTATION_DETAIL_TREE_LAYOUT
/// \file object_scope.h
/// \author Folling <mail@folling.io>
/// \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 <ikarus/macros.h>
#include <ikarus/types/object.h>
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

View file

@ -0,0 +1,63 @@
#pragma once
#include <ikarus/macros.h>
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

View file

@ -0,0 +1,31 @@
#pragma once
// IMPLEMENTATION_DETAIL_PROPERTY_TYPES
/// \file id.h
/// \author Folling <mail@folling.io>
#include <ikarus/macros.h>
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

View file

@ -0,0 +1,161 @@
#pragma once
// IMPLEMENTATION_DETAIL_PROPERTY_TYPES
#include <ikarus/macros.h>
#include <ikarus/stdtypes.h>
#include <ikarus/types/property_type.h>
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

7
src/CMakeLists.txt Normal file
View file

@ -0,0 +1,7 @@
file(
GLOB_RECURSE
FILES
"*.cpp"
)
set(SOURCE_FILES ${FILES} PARENT_SCOPE)

209
src/types/object_scope.cpp Normal file
View file

@ -0,0 +1,209 @@
#include "ikarus/types/object_scope.h"
#include <utility>
#include <catch2/catch_test_macros.hpp>
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<decltype(test) *>(data) = 1; },
[](IkarusEntity const * _, void * data) { *reinterpret_cast<decltype(test) *>(data) = 2; },
&test
);
REQUIRE(test == 1);
ikarus_property_scope_visit(
&property_entity_scope,
[](IkarusBlueprint const * _, void * data) { *reinterpret_cast<decltype(test) *>(data) = 1; },
[](IkarusEntity const * _, void * data) { *reinterpret_cast<decltype(test) *>(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<decltype(test) *>(data) = 1; },
[](IkarusPropertyScope const * _, void * data) { *reinterpret_cast<decltype(test) *>(data) = 2; },
[](IkarusEntityScope const * _, void * data) { *reinterpret_cast<decltype(test) *>(data) = 3; },
&test
);
REQUIRE(test == value);
}
}

337
src/types/value.cpp Normal file
View file

@ -0,0 +1,337 @@
#include "ikarus/types/value.h"
#include <cmath>
#include <limits>
#include <catch2/catch_test_macros.hpp>
/// \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<long double>::quiet_NaN());
REQUIRE(ikarus_value_is_invalid(&nan_value));
auto signaling_non_value = ikarus_value_create_number(std::numeric_limits<long double>::signaling_NaN());
REQUIRE(ikarus_value_is_invalid(&signaling_non_value));
auto inf_value = ikarus_value_create_number(std::numeric_limits<long double>::infinity());
REQUIRE(ikarus_value_is_invalid(&inf_value));
auto neg_inf_value = ikarus_value_create_number(-std::numeric_limits<long double>::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<decltype(test) *>(data) = 1; },
[](IkarusNumberValue const * _, void * data) { *reinterpret_cast<decltype(test) *>(data) = 2; },
[](IkarusTextValue const * _, void * data) { *reinterpret_cast<decltype(test) *>(data) = 3; },
&test
);
REQUIRE(test == expected);
}
}

2
vendor/CMakeLists.txt vendored Normal file
View file

@ -0,0 +1,2 @@
add_subdirectory(catch2)
add_subdirectory(sqlitecpp)

1
vendor/catch2 vendored Submodule

@ -0,0 +1 @@
Subproject commit 5bba3e4038602badb691da914523f667a2dd1f27

1
vendor/doxygen-awesome-css vendored Submodule

@ -0,0 +1 @@
Subproject commit 00a52f6c74065ffbd836cbd791ddfe8edf2836b8

1
vendor/sqlitecpp vendored Submodule

@ -0,0 +1 @@
Subproject commit afe7b165002ccf86a37da5b6b157ce4ff9db0401