Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
528c375
fix(UE): memory leaks
JBenda May 27, 2026
6c9ba6d
WIP
JBenda May 28, 2026
ffd0d53
WIP2
JBenda May 29, 2026
bae5097
WIP3
JBenda May 29, 2026
51348e7
fix(UE): fix UE 5.7 compile errors
JBenda May 29, 2026
20378e0
feat(UE): Allow migratable snapshots with sync snapshot taking
JBenda May 29, 2026
6e342b9
fix(globals): migration can also reduce the number of knots -> resize…
JBenda May 30, 2026
16dfa07
fix(Migration): list_table migration had a of by one error
JBenda May 31, 2026
25834ee
fix(List/Migration): list equivalence was calculated wrong
JBenda Jun 1, 2026
1402ce2
StartImplementing ListMigration correctly
JBenda Jun 3, 2026
30952c3
fix(ListTable): init reused list memory
JBenda Jun 3, 2026
452d866
FirstSctach: migrate list variables
JBenda Jun 3, 2026
43735cb
feat(Migration): implement list tables migration for variables
JBenda Jun 4, 2026
ae2a474
docs: Add documentation for new UE components, bump Doxygen to 1.17
JBenda Jun 4, 2026
98512b2
fix(python): add dropped argument lookaheadSafe for external function…
JBenda Jun 4, 2026
7b546c7
docs(UE): update UE demo for the new features (e.g. migratable snapsh…
JBenda Jun 4, 2026
a1eb4a2
test: restructure test cases to be more fitting with BDD
JBenda Jun 5, 2026
90b1b29
test: restructure test cases to be more fitting with BDD
JBenda Jun 5, 2026
03c5cd3
test: restructure test cases to be more fitting with BDD
JBenda Jun 5, 2026
65cb33d
WIP
JBenda Jun 5, 2026
0149a07
ci: bump github actions to support NodeJS 24
JBenda Jun 5, 2026
31d0b75
WIP
JBenda Jun 5, 2026
1f68685
WIP
JBenda Jun 7, 2026
b70cd9d
test: restructure test cases to be more fitting with BDD
JBenda Jun 8, 2026
bf63690
build(bump): add a script to auto bump inkcpp and ue version
JBenda Jun 8, 2026
29e506e
fix(migration): runtimes fetch_tags did not stored heap strings, but …
JBenda Jun 8, 2026
1e100c9
docs(UE): update InkCPP-DEMO for new features
JBenda Jun 8, 2026
1981215
style: formatting
JBenda Jun 9, 2026
742755a
ci(docu): cache doxygen version
JBenda Jun 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ jobs:
name: Build Doxygen documentation
needs: [compilation, build-python]
runs-on: ubuntu-latest
env:
DOXYGEN_VERSION: "1.17.0"
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -215,13 +217,19 @@ jobs:
pybind11-stubgen
pdoc
--user
- uses: actions/cache@v5
id: cache-doxygen
with:
path: doxygen-${{ env.DOXYGEN_VERSION }}.linux.bin.tar.gz
key: doxygen-${{ env.DOXYGEN_VERSION }}
- name: download Doxygen
if: steps.cache-doxygen.outputs.cache-hit != 'true'
run: wget https://www.doxygen.nl/files/doxygen-$DOXYGEN_VERSION.linux.bin.tar.gz
- name: Install Doxygen
run: |
sudo apt-get install graphviz -y
wget https://www.doxygen.nl/files/doxygen-1.10.0.linux.bin.tar.gz
gunzip doxygen-*.tar.gz
tar xf doxygen-*.tar
cd doxygen-1.10.0/
tar xzf doxygen-$DOXYGEN_VERSION.linux.bin.tar.gz
cd doxygen-$DOXYGEN_VERSION/
sudo make install
- name: Setup cmake
uses: jwlawson/actions-setup-cmake@v2
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ build/*
bin/
Bin/

# python build artifacts
dist/
__pycache__/
inkcpp_py.egg-info/

# Visual Studio
/out/build/
.vs
Binary file modified Documentation/unreal/InkCPP_DEMO.zip
Binary file not shown.
Binary file added Documentation/unreal/imgs/SaveGame.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
829 changes: 551 additions & 278 deletions Doxyfile

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion inkcpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ endforeach()
foreach(file IN LISTS PUBLIC_HEADERS)
get_filename_component(file "${file}" NAME)
configure_file("include/${file}"
"${CMAKE_BINARY_DIR}/unreal/inkcpp/Source/inkcpp/Public/ink/${FILE}" COPYONLY)
"${CMAKE_BINARY_DIR}/unreal/inkcpp/Source/inkcpp/Public/ink/${file}" COPYONLY)
endforeach()
foreach(file IN LISTS COLLECTION_SOURCES)
configure_file("${file}" "${CMAKE_BINARY_DIR}/unreal/inkcpp/Source/inkcpp/Private/ink/${file}"
Expand Down
70 changes: 56 additions & 14 deletions inkcpp/array.h
Original file line number Diff line number Diff line change
Expand Up @@ -346,13 +346,16 @@ class basic_restorable_array : public snapshot_interface
void clear(const T& value);

// snapshot interface
virtual bool can_be_migrated() const;
virtual size_t snap(unsigned char* data, const snapper&) const;
virtual const unsigned char* snap_load(const unsigned char* data, const loader&);
bool can_be_migrated() const;
size_t snap(unsigned char* data, const snapper&) const;
virtual const unsigned char* snap_load(const unsigned char* data, const loader&) = 0;

protected:
inline T* buffer() { return _array; }

const unsigned char* impl_snap_load_meta(const unsigned char* data);
const unsigned char* impl_snap_load_payload(const unsigned char* data);

void set_new_buffer(T* buffer, size_t capacity)
{
_array = buffer;
Expand Down Expand Up @@ -455,6 +458,8 @@ inline void basic_restorable_array<T>::forget()
// Clear
_temp[i] = _null;
}

_saved = false;
}

template<typename T>
Expand All @@ -480,13 +485,18 @@ inline void basic_restorable_array<T>::clear(const T& value)
template<typename T, size_t SIZE>
class fixed_restorable_array : public basic_restorable_array<T>
{
using base = basic_restorable_array<T>;

public:
fixed_restorable_array(const T& initial, const T& nullValue)
: basic_restorable_array<T>(_buffer, SIZE * 2, nullValue)
{
basic_restorable_array<T>::clear(initial);
}

const unsigned char*
snap_load(const unsigned char* data, const snapshot_interface::loader&) override;

private:
T _buffer[SIZE * 2];
};
Expand Down Expand Up @@ -519,7 +529,7 @@ class allocated_restorable_array : public basic_restorable_array<T>
size_t new_capacity = 2 * n;
T* new_buffer = new T[new_capacity];
if (_buffer) {
for (size_t i = 0; i < base::capacity(); ++i) {
for (size_t i = 0; i < base::capacity() && i < n; ++i) {
new_buffer[i] = _buffer[i];
// copy temp
new_buffer[i + n] = _buffer[i + base::capacity()];
Expand All @@ -543,6 +553,9 @@ class allocated_restorable_array : public basic_restorable_array<T>
}
}

const unsigned char*
snap_load(const unsigned char* data, const snapshot_interface::loader&) override;

private:
T _initialValue;
T _nullValue;
Expand Down Expand Up @@ -571,26 +584,55 @@ inline size_t basic_restorable_array<T>::snap(unsigned char* data, const snapper
}

template<typename T>
inline const unsigned char*
basic_restorable_array<T>::snap_load(const unsigned char* data, const loader&)
inline const unsigned char* basic_restorable_array<T>::impl_snap_load_meta(const unsigned char* data
)
{
auto ptr = data;
ptr = snap_read(ptr, _saved);
ptr = snap_read(ptr, _loaded_capacity);
if (buffer() == nullptr) {
static_cast<allocated_restorable_array<T>&>(*this).resize(_loaded_capacity);
}
inkAssert(
_capacity >= _loaded_capacity,
"New config does not allow for necessary size used by this snapshot!"
);
T null;
ptr = snap_read(ptr, null);
inkAssert(null == _null, "null value is different to snapshot!");
for (size_t i = 0; i < _loaded_capacity; ++i) {
return ptr;
}

template<typename T>
inline const unsigned char*
basic_restorable_array<T>::impl_snap_load_payload(const unsigned char* data)
{
inkAssert(
capacity() >= loaded_capacity(),
"New config does not allow for necessary size used by this snapshot!"
);
auto ptr = data;
for (size_t i = 0; i < loaded_capacity(); ++i) {
ptr = snap_read(ptr, _array[i]);
ptr = snap_read(ptr, _temp[i]);
}
return ptr;
}

template<typename T, size_t SIZE>
inline const unsigned char* fixed_restorable_array<
T, SIZE>::snap_load(const unsigned char* data, const snapshot_interface::loader&)
{
auto ptr = data;
ptr = base::impl_snap_load_meta(ptr);
ptr = base::impl_snap_load_payload(ptr);
return ptr;
}

template<typename T>
inline const unsigned char* allocated_restorable_array<
T>::snap_load(const unsigned char* data, const snapshot_interface::loader&)
{
auto ptr = data;
ptr = base::impl_snap_load_meta(ptr);
if (base::buffer() == nullptr || base::capacity() < base::loaded_capacity()) {
resize(base::loaded_capacity());
}
ptr = base::impl_snap_load_payload(ptr);
return ptr;
}

} // namespace ink::runtime::internal
2 changes: 1 addition & 1 deletion inkcpp/collections/restorable.h
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ class restorable : public snapshot_interface

// Forward iterate
template<typename CallbackMethod, typename IsNullPredicate>
void for_each(CallbackMethod callback, IsNullPredicate isNull) const
void for_each(CallbackMethod callback, IsNullPredicate isNull)
{
if (_pos == 0) {
return;
Expand Down
1 change: 1 addition & 0 deletions inkcpp/container_operations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ void operation<Command::CHOICE_COUNT, value_type::none, void>::operator()(
basic_eval_stack& stack, value* vals
)
{
( void ) vals;
stack.push(value{}.set<value_type::int32>(static_cast<int32_t>(_runner.num_choices())));
}

Expand Down
61 changes: 31 additions & 30 deletions inkcpp/globals_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,33 +26,17 @@ globals_impl::globals_impl(const story_impl* story)
_visit_counts.resize(_num_containers);
if (_lists) {
// initialize static lists
init_static_list_flags();
_lists.init_static_list_flags(_owner->lists(), _variables);
}
}

void globals_impl::init_static_list_flags()
void globals_impl::visit(uint32_t container_id, bool preserve_turns)
{
const list_flag* flags = _owner->lists();
while (*flags != null_flag) {
list_table::list l = _lists.create_permament();
while (*flags != null_flag) {
list_flag flag = _lists.external_fvalue_to_internal(*flags);
_lists.add_inplace(l, flag);
++flags;
}
++flags;
}
for (const auto& flag : _lists.named_flags()) {
set_variable(
hash_string(flag.name),
value{}.set<value_type::list_flag>(list_flag{flag.flag.list_id, flag.flag.flag})
);
}
}

void globals_impl::visit(uint32_t container_id)
{
_visit_counts.set(container_id, {_visit_counts[container_id].visits + 1, 0});
const int32_t existing_turns = _visit_counts[container_id].turns;
_visit_counts.set(
container_id, {_visit_counts[container_id].visits + (preserve_turns ? 0 : 1),
preserve_turns ? existing_turns : 0}
);
}

uint32_t globals_impl::visits(uint32_t container_id) const
Expand Down Expand Up @@ -294,6 +278,9 @@ const unsigned char* globals_impl::snap_load(const unsigned char* ptr, const loa
_visit_counts.resize(_owner->num_containers());
}
_visit_counts.save();
for (size_t i = 0; i < old_capacity; ++i) {
_visit_counts.set(i, visit_count());
}
}

inkAssert(
Expand All @@ -302,8 +289,8 @@ const unsigned char* globals_impl::snap_load(const unsigned char* ptr, const loa
);
for (size_t i = 0; i < old_capacity; ++i) {
hash_t path;
ptr = snap_read(ptr, path);
container_t c_id;
ptr = snap_read(ptr, path);
container_t c_id = ~0U;
ip_t container_ip = _owner->find_offset_for(path);
bool found = container_ip != nullptr
&& _owner->find_container_id(
Expand All @@ -320,6 +307,7 @@ const unsigned char* globals_impl::snap_load(const unsigned char* ptr, const loa
}
if (loader.migratable) {
_visit_counts.forget();
_visit_counts.resize(_num_containers);
}
inkAssert(
_num_containers == _visit_counts.capacity(),
Expand All @@ -331,15 +319,28 @@ const unsigned char* globals_impl::snap_load(const unsigned char* ptr, const loa
return ptr;
}

bool globals_impl::migrate_new_globals(globals_impl& new_globals, const char* list_metadata)
bool globals_impl::migrate_new_globals(
const loader& loader, globals_impl& new_globals, const char* list_metadata
)
{
bool success
= _variables.migrate(new_globals._variables) && ((! _lists) || _lists.migrate(list_metadata));
if (! success) {
if (! _variables.migrate(new_globals._variables)) {
return false;
}
if (_lists && ! loader.old_ref_table) {
if (! _lists.create_match_lut(
list_metadata, loader.list_list_matches, loader.list_value_matches, loader.old_ref_table
)) {
return false;
}
}
if (_lists) {
init_static_list_flags();
_lists.init_static_list_flags(_owner->lists(), _variables);
if (! _lists.migrate_variables(
loader.list_old_new_map, loader.list_list_matches, loader.list_value_matches,
*loader.old_ref_table, _variables
)) {
return false;
}
}
return true;
}
Expand Down
12 changes: 7 additions & 5 deletions inkcpp/globals_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ class globals_impl final
{
friend snapshot_impl;

void init_static_list_flags();

public:
size_t snap(unsigned char* data, const snapper&) const;
const unsigned char* snap_load(const unsigned char* data, const loader&);
Expand All @@ -45,7 +43,9 @@ class globals_impl final
* @param[in] list_metadata old list metadata to migrate list
* the globals stored inside.
*/
bool migrate_new_globals(globals_impl& new_globals, const char* list_metadata);
bool migrate_new_globals(
const loader& loader, globals_impl& new_globals, const char* list_metadata
);
// Initializes a new global store from the given story
globals_impl(const story_impl*);

Expand All @@ -64,8 +64,10 @@ class globals_impl final
void internal_observe(hash_t name, internal::callback_base* callback) override;

public:
// Records a visit to a container
void visit(uint32_t container_id);
// Records a visit to a container.
// If preserve_turns is true the existing turns-since counter is kept intact
// (used during snapshot migration to avoid clobbering the restored value).
void visit(uint32_t container_id, bool preserve_turns = false);

// Checks the number of visits to a container
uint32_t visits(uint32_t container_id) const;
Expand Down
2 changes: 1 addition & 1 deletion inkcpp/header.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace ink::internal
{

bool header::verify() const
bool header::validate() const
{
if (endian() == endian_types::none) {
inkFail("Header magic number was wrong!");
Expand Down
3 changes: 2 additions & 1 deletion inkcpp/include/functional.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,9 @@ class function : public function_base
if constexpr (traits::arity == 2) {
return is_same<
const ink::runtime::value*, typename traits::template argument<1>::type>::value;
} else {
return false;
}
return false;
}

template<size_t... Is>
Expand Down
10 changes: 7 additions & 3 deletions inkcpp/include/list.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class list_interface
{
}

/** copy assigment operator. */
virtual list_interface& operator=(const list_interface&) = default;

virtual ~list_interface() {}
Expand Down Expand Up @@ -81,6 +82,9 @@ class list_interface
}

public:
/** copy constructor. */
iterator(const iterator&) = default;

/** contains flag data */
struct Flag {
const char* flag_name; ///< name of the flag
Expand Down Expand Up @@ -127,9 +131,8 @@ class list_interface
# pragma GCC diagnostic ignored "-Wunused-parameter"
#else
# pragma warning(push)
# pragma warning( \
disable : 4100, justification : "non functional prototypes do not need the argument." \
)
// non functional prototypes do not need the argument.
# pragma warning(disable : 4100)
#endif

/** checks if a flag is contained in the list */
Expand Down Expand Up @@ -203,6 +206,7 @@ class list_interface

/** @private */
internal::list_table* _list_table;
/** @private */
int _list;
};

Expand Down
Loading
Loading