diff --git a/inkcpp/globals_impl.cpp b/inkcpp/globals_impl.cpp index 47037d27..76621f4a 100644 --- a/inkcpp/globals_impl.cpp +++ b/inkcpp/globals_impl.cpp @@ -5,12 +5,12 @@ #include "system.h" #include "types.h" -#include namespace ink::runtime::internal { globals_impl::globals_impl(const story_impl* story) : _num_containers(story->num_containers()) + , _turn_cnt{0} , _visit_counts() , _owner(story) , _runners_start(nullptr) @@ -39,10 +39,12 @@ namespace ink::runtime::internal } } - void globals_impl::visit(uint32_t container_id) + void globals_impl::visit(uint32_t container_id, bool entering_at_start) { - _visit_counts[container_id].visits += 1; - _visit_counts[container_id].turns = 0; + if((!(_owner->container_flag(container_id) & CommandFlag::CONTAINER_MARKER_ONLY_FIRST)) || entering_at_start) { + _visit_counts[container_id].visits += 1; + _visit_counts[container_id].turns = 0; + } } uint32_t globals_impl::visits(uint32_t container_id) const @@ -50,8 +52,13 @@ namespace ink::runtime::internal return _visit_counts[container_id].visits; } + uint32_t globals_impl::turns() const { + return _turn_cnt; + } + void globals_impl::turn() { + ++_turn_cnt; for(size_t i = 0; i < _visit_counts.size(); ++i) { if(_visit_counts[i].turns != -1) { diff --git a/inkcpp/globals_impl.h b/inkcpp/globals_impl.h index d6d4fc1c..a8e2f0bc 100644 --- a/inkcpp/globals_impl.h +++ b/inkcpp/globals_impl.h @@ -37,10 +37,13 @@ namespace ink::runtime::internal public: // Records a visit to a container - void visit(uint32_t container_id); + /// @param start_cmd iff the visit was initiatet through a MARKER_START_CONTAINER + void visit(uint32_t container_id, bool entering_at_start); // Checks the number of visits to a container uint32_t visits(uint32_t container_id) const; + // Number of current turn (number of passed choices) + uint32_t turns() const; // Returnn number of turns since container was last visited // \retval -1 if container was never visited before @@ -85,6 +88,7 @@ namespace ink::runtime::internal // Store the number of containers. This is the length of most of our lists const uint32_t _num_containers; + uint32_t _turn_cnt = 0; // Visit count array struct visit_count { uint32_t visits = 0; diff --git a/inkcpp/numeric_operations.h b/inkcpp/numeric_operations.h index 3a1068a1..8d909790 100644 --- a/inkcpp/numeric_operations.h +++ b/inkcpp/numeric_operations.h @@ -427,7 +427,7 @@ namespace ink::runtime::internal { using operation_base::operation_base; void operator()(basic_eval_stack& stack, value* vals) { int min = casting::numeric_cast(vals[0]); - int max = casting::numeric_cast(vals[0]); + int max = casting::numeric_cast(vals[1]); stack.push(value{}.set(static_cast(_prng.rand(max - min + 1) + min))); } }; diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index f6685bd4..ff983bbd 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -6,6 +6,7 @@ #include "header.h" #include "string_utils.h" #include "snapshot_impl.h" +#include "system.h" #include "value.h" namespace ink::runtime @@ -174,94 +175,94 @@ namespace ink::runtime::internal // _should be_ able to safely assume that there is nothing to do here. A falling // divert should only be taking us from a container to that same container's end point // without entering any other containers - /*if (_is_falling) + // OR IF if target is same position do nothing + // could happend if jumping to and of an unnamed container + if (_is_falling || dest ==_ptr) { _ptr = dest; return; - }*/ - - // Check which direction we are jumping - bool reverse = dest < _ptr; + } - // iteration const uint32_t* iter = nullptr; - container_t container_id; + container_t id; ip_t offset; - bool inBound = false; + size_t comm_end; + bool reversed = _ptr > dest; + + if (reversed) { + comm_end = 0; + iter = nullptr; + const ContainerData* old_iter = nullptr; + const uint32_t* last_comm_iter = nullptr; + _container.rev_iter(old_iter); + + // find commen part of old and new stack + while(_story->iterate_containers(iter, id, offset)) { + if(old_iter == nullptr || offset >= dest) { break; } + if(old_iter !=nullptr && id == old_iter->id) { + last_comm_iter = iter; + _container.rev_iter(old_iter); + ++comm_end; + } + } - // Iterate until we find the container marker just before our own - while (_story->iterate_containers(iter, container_id, offset, reverse)) { - if (( !reverse && offset > _ptr ) - || ( reverse && offset < _ptr )) { + // clear old part from stack + while(_container.size() > comm_end) { _container.pop(); } + iter = last_comm_iter; - // Step back once in the iteration and break - inBound = true; - _story->iterate_containers(iter, container_id, offset, !reverse); - break; + } else { + iter = nullptr; + comm_end = _container.size(); + // go to current possition in container list + while(_story->iterate_containers(iter, id, offset)) { + if(offset >= _ptr) {break;} } + _story->iterate_containers(iter, id, offset, true); } - size_t pos = _container.size(); - - bool first = true; - // Start moving forward (or backwards) - if(inBound && (offset == nullptr || (!reverse&&offset<=dest) || (reverse&&offset>dest)) ) - while (_story->iterate_containers(iter, container_id, offset, reverse)) - { - // Break when we've past the destination - if ((!reverse && offset > dest) || (reverse && offset <= dest)) { - // jump back to start of same container - if(first && reverse && offset == dest - && _container.top() == container_id) { - // check if it was start flag - auto con_id = container_id; - _story->iterate_containers(iter, container_id, offset, true); - if(offset == nullptr || con_id == container_id) - { - _globals->visit(container_id); - } - } - break; - } - first = false; - - // Two cases: - - // (1) Container iterator has the same value as the top of the stack. - // This means that this is an end marker for the container we're in - if (!_container.empty() && _container.top() == container_id) - { - if (_container.size() == pos) - pos--; - - // Get out of that container - _container.pop(); - } - - // (2) This must be the entrance marker for a new container. Enter it - else - { - // Push it - _container.push(container_id); + // move to destination and update container stack on the go + while(_story->iterate_containers(iter, id, offset)) { + if (offset >= dest) { break; } + if(_container.empty() || _container.top().id != id) { + _container.push({.id = id, .offset = offset}); + } else { + _container.pop(); + if (_container.size() < comm_end) { + comm_end = _container.size(); } } + } + _ptr = dest; - // Iterate over the container stack marking any _new_ entries as "visited" - if (record_visits) - { - const container_t* con_iter; - size_t num_new = _container.size() - pos; - while (_container.iter(con_iter)) + // if we jump directly to a named container start, go inside, if its a ONLY_FIRST container + // it will get visited in the next step + if (offset == dest && static_cast(offset[0]) == Command::START_CONTAINER_MARKER) { + _ptr += 6; + _container.push({.id=id, .offset=offset}); + if (reversed && comm_end == _container.size() - 1) { ++comm_end; } + } + + // iff all container (until now) are entered at first position + bool allEnteredAtStart = true; + ip_t child_position = dest; + if(record_visits) { + const ContainerData* iter = nullptr; + size_t level = _container.size(); + while(_container.iter(iter) && + (level > comm_end || _story->container_flag(iter->offset) & CommandFlag::CONTAINER_MARKER_ONLY_FIRST )) { - if (num_new <= 0) - break; - _globals->visit(*con_iter); - --num_new; + auto offset = iter->offset; + inkAssert(child_position >= offset, "Container stack order is broken"); + // 6 == len of START_CONTAINER_SIGNAL, if its 6 bytes behind the container it is a unnnamed subcontainers first child + // check if child_positino is the first child of current container + allEnteredAtStart = allEnteredAtStart && ((child_position - offset) <= 6); + child_position = offset; + _globals->visit(iter->id, allEnteredAtStart); } } - // Jump - _ptr = dest; + + } template void runner_impl::start_frame(uint32_t target) { @@ -279,7 +280,7 @@ namespace ink::runtime::internal // Do the jump inkAssert(_story->instructions() + target < _story->end(), "Diverting past end of story data!"); - jump(_story->instructions() + target); + jump(_story->instructions() + target, true); } frame_type runner_impl::execute_return() @@ -327,7 +328,7 @@ namespace ink::runtime::internal *global.cast(), *data, static_cast(*this)), - _backup(nullptr), _done(nullptr), _choices(), _container(~0), _rng(time(NULL)) + _backup(nullptr), _done(nullptr), _choices(), _container(ContainerData{}), _rng(time(NULL)) { _ptr = _story->instructions(); _evaluation_mode = false; @@ -508,8 +509,7 @@ namespace ink::runtime::internal _threads.clear(); // Jump to destination and clear choice list - jump(_story->instructions() + c.path(), false); - if(!_container.empty()){ _globals->visit(_container.top()); } + jump(_story->instructions() + c.path(), true); clear_choices(); clear_tags(); } @@ -663,7 +663,7 @@ namespace ink::runtime::internal // Clear state and move to destination reset(); _ptr = _story->instructions(); - jump(destination); + jump(destination, false); return true; } @@ -873,7 +873,7 @@ namespace ink::runtime::internal uint32_t target = read(); // Check for condition - if (flag & CommandFlag::DIVERT_HAS_CONDITION && !_eval.pop().get()) + if (flag & CommandFlag::DIVERT_HAS_CONDITION && !_eval.pop().truthy(_globals->lists())) break; // SPECIAL: Fallthrough divert. We're starting to fall out of containers @@ -908,7 +908,7 @@ namespace ink::runtime::internal // Do the jump inkAssert(_story->instructions() + target < _story->end(), "Diverting past end of story data!"); - jump(_story->instructions() + target); + jump(_story->instructions() + target, true); } break; case Command::DIVERT_TO_VARIABLE: @@ -917,14 +917,14 @@ namespace ink::runtime::internal hash_t variable = read(); // Check for condition - if (flag & CommandFlag::DIVERT_HAS_CONDITION && !_eval.pop().get()) + if (flag & CommandFlag::DIVERT_HAS_CONDITION && !_eval.pop().truthy(_globals->lists())) break; const value* val = get_var(variable); inkAssert(val, "Jump destiniation needs to be defined!"); // Move to location - jump(_story->instructions() + val->get()); + jump(_story->instructions() + val->get(), true); inkAssert(_ptr < _story->end(), "Diverted past end of story data!"); } break; @@ -1147,7 +1147,7 @@ namespace ink::runtime::internal // Choice is conditional if (flag & CommandFlag::CHOICE_HAS_CONDITION) { // Only show if the top of the eval stack is 'truthy' - if (!_eval.pop().get()) + if(!_eval.pop().truthy(_globals->lists())) break; } @@ -1185,20 +1185,21 @@ namespace ink::runtime::internal case Command::START_CONTAINER_MARKER: { // Keep track of current container - _container.push(read()); + auto index = read(); + // offset points to command, command has size 6 + _container.push({.id=index, .offset=_ptr - 6}); // Increment visit count - if (flag & CommandFlag::CONTAINER_MARKER_TRACK_VISITS) + if (flag & CommandFlag::CONTAINER_MARKER_TRACK_VISITS || flag & CommandFlag::CONTAINER_MARKER_TRACK_TURNS) { - _globals->visit(_container.top()); + _globals->visit(_container.top().id, true); } - // TODO Turn counts } break; case Command::END_CONTAINER_MARKER: { container_t index = read(); - inkAssert(_container.top() == index, "Leaving container we are not in!"); + inkAssert(_container.top().id == index, "Leaving container we are not in!"); // Move up out of the current container _container.pop(); @@ -1222,7 +1223,7 @@ namespace ink::runtime::internal // HACK _ptr += sizeof(Command) + sizeof(CommandFlag); execute_return(); - } else if (_container.empty() && _ptr == _story->end()){ + } else if (_ptr == _story->end()){ // check needed, because it colud exist an unnamed toplevel container (empty named container stack != empty container stack) on_done(true); } } @@ -1231,7 +1232,11 @@ namespace ink::runtime::internal { // Push the visit count for the current container to the top // is 0-indexed for some reason. idk why but this is what ink expects - _eval.push(value{}.set((int)_globals->visits(_container.top()) - 1)); + _eval.push(value{}.set((int)_globals->visits(_container.top().id) - 1)); + } break; + case Command::TURN: + { + _eval.push(value{}.set((int)_globals->turns())); } break; case Command::SEQUENCE: { diff --git a/inkcpp/runner_impl.h b/inkcpp/runner_impl.h index 590d4945..44396d07 100644 --- a/inkcpp/runner_impl.h +++ b/inkcpp/runner_impl.h @@ -142,7 +142,7 @@ namespace ink::runtime::internal void clear_tags(); // Special code for jumping from the current IP to another - void jump(ip_t, bool record_visits = true); + void jump(ip_t, bool record_visits); void run_binary_operator(unsigned char cmd); void run_unary_operator(unsigned char cmd); @@ -257,7 +257,15 @@ namespace ink::runtime::internal functions _functions; // Container set - internal::managed_restorable_stack _container; + struct ContainerData { + container_t id = ~0u; + ip_t offset = 0; + bool operator==(const ContainerData& oth) const { + return oth.id == id && oth.offset == offset; + } + bool operator!=(const ContainerData& oth) const { return !(*this == oth); } + }; + internal::managed_restorable_stack _container; bool _is_falling = false; bool _saved = false; diff --git a/inkcpp/simple_restorable_stack.h b/inkcpp/simple_restorable_stack.h index cacb8806..94b6c3d0 100644 --- a/inkcpp/simple_restorable_stack.h +++ b/inkcpp/simple_restorable_stack.h @@ -6,6 +6,8 @@ namespace ink::runtime::internal { + /// only use this type for simple objects with simple copy operator and no heap references + /// because they will may be serialized, stored and loaded in a different instance template class simple_restorable_stack : public snapshot_interface { @@ -24,6 +26,7 @@ namespace ink::runtime::internal void clear(); bool iter(const T*& iterator) const; + bool rev_iter(const T*& iterator) const; // == Save/Restore == void save(); @@ -164,7 +167,15 @@ namespace ink::runtime::internal // Begin at the top of the stack if (iterator == nullptr || iterator < _buffer || iterator > _buffer + _pos) { - iterator = _buffer + _pos - 1; + if (_pos == _save) { + if(_jump == 0) { + iterator = nullptr; + return false; + } + iterator = _buffer + _jump -1; + } else { + iterator = _buffer + _pos - 1; + } return true; } @@ -175,9 +186,6 @@ namespace ink::runtime::internal // Run backwards iterator--; - // Skip nulls - while (*iterator == _null) - iterator--; // End if (iterator < _buffer) @@ -189,6 +197,35 @@ namespace ink::runtime::internal return true; } + template + inline bool simple_restorable_stack::rev_iter(const T*& iterator) const + { + if (_pos == 0) + return false; + if (iterator == nullptr || iterator < _buffer || iterator > _buffer + _pos) { + if (_jump == 0) { + if (_save == _pos) { + iterator = nullptr; + return false; + } + iterator = _buffer + _save; + } else { + iterator = _buffer; + } + return true; + } + ++iterator; + if (iterator == _buffer + _jump) { + iterator = _buffer + _save; + } + + if(iterator == _buffer + _pos) { + iterator = nullptr; + return false; + } + return true; + } + template inline void simple_restorable_stack::save() { @@ -230,7 +267,6 @@ namespace ink::runtime::internal template size_t simple_restorable_stack::snap(unsigned char* data, const snapper&) const { - static_assert(is_same{}() || is_same{}() || is_same{}()); unsigned char* ptr = data; bool should_write = data != nullptr; ptr = snap_write(ptr, _null, should_write); @@ -250,7 +286,6 @@ namespace ink::runtime::internal template const unsigned char* simple_restorable_stack::snap_load(const unsigned char* ptr, const loader& loader) { - static_assert(is_same{}() || is_same{}() || is_same{}()); T null; ptr = snap_read(ptr, null); inkAssert(null == _null, "different null value compared to snapshot!"); diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index c7a1a6a6..9d96c1e8 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -154,6 +154,25 @@ namespace ink::runtime::internal return false; } + + CommandFlag story_impl::container_flag(ip_t offset) const { + inkAssert(static_cast(offset[0]) == Command::START_CONTAINER_MARKER || + static_cast(offset[0]) == Command::END_CONTAINER_MARKER); + return static_cast(offset[1]); + } + CommandFlag story_impl::container_flag(container_t id) const { + const uint32_t* iter = nullptr; + ip_t offset; + container_t c_id; + while(iterate_containers(iter, c_id, offset)) { + if (c_id == id) { + inkAssert(static_cast(offset[0]) == Command::START_CONTAINER_MARKER); + return static_cast(offset[1]); + } + } + inkAssert("Container not found -> can't fetch flag"); + } + ip_t story_impl::find_offset_for(hash_t path) const { hash_t* iter = _container_hash_start; diff --git a/inkcpp/story_impl.h b/inkcpp/story_impl.h index 2bfa322e..172c16a2 100644 --- a/inkcpp/story_impl.h +++ b/inkcpp/story_impl.h @@ -2,6 +2,7 @@ #include #include +#include "command.h" #include "types.h" #include "story.h" #include "header.h" @@ -35,6 +36,9 @@ namespace ink::runtime::internal bool iterate_containers(const uint32_t*& iterator, container_t& index, ip_t& offset, bool reverse = false) const; bool get_container_id(ip_t offset, container_t& container_id) const; + /// Get container flag from container offset (either start or end) + CommandFlag container_flag(ip_t offset) const; + CommandFlag container_flag(container_t id) const; ip_t find_offset_for(hash_t path) const; diff --git a/inkcpp/value.cpp b/inkcpp/value.cpp index 3a8b9af5..30962872 100644 --- a/inkcpp/value.cpp +++ b/inkcpp/value.cpp @@ -5,9 +5,100 @@ #include "list_table.h" #include "string_utils.h" #include "string_table.h" +#include "system.h" namespace ink::runtime::internal { + + template + bool truthy_impl(const value& v, const list_table& lists); + + template<> + bool truthy_impl(const value& v, const list_table& lists) { + inkAssert("Type was not found in operational types or it has no conversion to boolean"); + } + + template<> + bool truthy_impl(const value& v, const list_table& lists) { + if(v.type() == value_type::string) { + // if string is not empty + return *v.get().str != 0; + } else { + return truthy_impl(v, lists); + } + } + + template<> + bool truthy_impl(const value& v, const list_table& lists) { + // if list is not empty -> valid flag -> filled list + if(v.type() == value_type::list_flag) { + auto flag = v.get(); + return flag != null_flag && flag != empty_flag; + } else { + return truthy_impl(v, lists); + } + } + + template<> + bool truthy_impl(const value& v, const list_table& lists) { + // if list is not empty + if(v.type() == value_type::list) { + return lists.count(v.get()) > 0; + } else { + return truthy_impl(v, lists); + } + } + + template<> + bool truthy_impl(const value& v, const list_table& lists) { + if (v.type() == value_type::float32) { + return v.get() != 0.0f; + } else { + return truthy_impl(v, lists); + } + } + template<> + bool truthy_impl(const value& v, const list_table& lists) { + if(v.type() == value_type::int32) { + return v.get() != 0; + } else { + return truthy_impl(v, lists); + } + } + + template<> + bool truthy_impl(const value& v, const list_table& lists) { + if (v.type() == value_type::uint32) { + return v.get() != 0; + } else { + return truthy_impl(v, lists); + } + } + + template<> + bool truthy_impl(const value& v, const list_table& lists) { + if(v.type() == value_type::boolean) { + return v.get(); + } else { + return truthy_impl(v, lists); + } + } + + template<> + bool truthy_impl(const value& v, const list_table& lists) { + if (v.type() == value_type::divert) { + inkAssert("Divert can not be evaluated to boolean"); + } else { + return truthy_impl(v, lists); + } + } + + bool value::truthy(const list_table& lists) const { + return truthy_impl(*this, lists); + } + + + #ifdef INK_ENABLE_STL template void append(std::ostream& os, const value& val, const list_table* lists) { diff --git a/inkcpp/value.h b/inkcpp/value.h index 2e215296..19f7567f 100644 --- a/inkcpp/value.h +++ b/inkcpp/value.h @@ -106,6 +106,8 @@ namespace ink::runtime::internal { typename ret::type get() const { static_assert(ty != ty, "No getter for this type defined!"); } + /// check if value evaluates to true + bool truthy(const list_table& lists) const; /// set value of type (if possible) template constexpr value& set(Args ...args) { diff --git a/inkcpp_compiler/binary_emitter.cpp b/inkcpp_compiler/binary_emitter.cpp index 05c86371..1e7c3a5d 100644 --- a/inkcpp_compiler/binary_emitter.cpp +++ b/inkcpp_compiler/binary_emitter.cpp @@ -54,6 +54,7 @@ namespace ink::compiler::internal // Offset in the binary stream uint32_t offset = 0; + uint32_t end_offset = 0; // Index used in CNT? operations container_t counter_index = ~0; @@ -121,6 +122,7 @@ namespace ink::compiler::internal uint32_t binary_emitter::end_container() { // Move up the chain + _current->end_offset = _containers.pos(); _current = _current->parent; // Return offset diff --git a/inkcpp_compiler/command.cpp b/inkcpp_compiler/command.cpp index 47a2b114..c8cea04a 100644 --- a/inkcpp_compiler/command.cpp +++ b/inkcpp_compiler/command.cpp @@ -34,6 +34,7 @@ namespace ink "du", "inkcpp_PUSH_VARIABLE_VALUE", "visit", + "turn", "inkcpp_READ_COUNT", "seq", "srnd", diff --git a/inkcpp_compiler/json_compiler.cpp b/inkcpp_compiler/json_compiler.cpp index d1bed8c7..811b38dd 100644 --- a/inkcpp_compiler/json_compiler.cpp +++ b/inkcpp_compiler/json_compiler.cpp @@ -1,11 +1,11 @@ #include "json_compiler.h" +#include "command.h" #include "list_data.h" #include "system.h" #include "version.h" #include -#include namespace ink::compiler::internal { @@ -52,6 +52,7 @@ namespace ink::compiler::internal container_t indexToReturn = ~0; bool recordInContainerMap = false; vector deferred; + CommandFlag cmd_flags = CommandFlag::NO_FLAGS; }; void json_compiler::handle_container_metadata( @@ -91,14 +92,14 @@ namespace ink::compiler::internal container_t myIndex = _next_container_index++; // Make appropriate flags - CommandFlag cmd_flags = CommandFlag::NO_FLAGS; + data.cmd_flags = CommandFlag::NO_FLAGS; if (visits) - cmd_flags |= CommandFlag::CONTAINER_MARKER_TRACK_VISITS; + data.cmd_flags |= CommandFlag::CONTAINER_MARKER_TRACK_VISITS; if (turns) - cmd_flags |= CommandFlag::CONTAINER_MARKER_TRACK_TURNS; + data.cmd_flags |= CommandFlag::CONTAINER_MARKER_TRACK_TURNS; + if (onlyFirst) + data.cmd_flags |= CommandFlag::CONTAINER_MARKER_ONLY_FIRST; - // Write command out at this position - _emitter->write(Command::START_CONTAINER_MARKER, myIndex, cmd_flags); data.indexToReturn = myIndex; @@ -128,6 +129,10 @@ namespace ink::compiler::internal // tell the emitter we're beginning a new container uint32_t position = _emitter->start_container(index_in_parent, name_override.empty() ? meta.name : name_override); + // Write command out at this position + if(meta.cmd_flags != CommandFlag::NO_FLAGS) { + _emitter->write(Command::START_CONTAINER_MARKER, meta.indexToReturn, meta.cmd_flags); + } if(meta.recordInContainerMap) { _emitter->add_start_to_container_map(position, meta.indexToReturn); } @@ -218,13 +223,13 @@ namespace ink::compiler::internal _emitter->patch_fallthroughs(offset); } - // Write end container marker - if (meta.indexToReturn != ~0) - _emitter->write(Command::END_CONTAINER_MARKER, meta.indexToReturn); - // End container uint32_t end_position = _emitter->end_container(); + // Write end container marker, End pointer should point to End command (form symetry with START command) + if (meta.indexToReturn != ~0) + _emitter->write(Command::END_CONTAINER_MARKER, meta.indexToReturn, meta.cmd_flags); + // Record end position in map if (meta.recordInContainerMap) _emitter->add_end_to_container_map(end_position, meta.indexToReturn); diff --git a/shared/private/command.h b/shared/private/command.h index ac266791..881963ea 100644 --- a/shared/private/command.h +++ b/shared/private/command.h @@ -42,6 +42,7 @@ namespace ink DUPLICATE, PUSH_VARIABLE_VALUE, VISIT, + TURN, /// How many choices where made since start of the story READ_COUNT, SEQUENCE, SEED, @@ -135,6 +136,7 @@ namespace ink // == Container marker CONTAINER_MARKER_TRACK_VISITS = 1 << 0, CONTAINER_MARKER_TRACK_TURNS = 1 << 1, + CONTAINER_MARKER_ONLY_FIRST = 1 << 2, // == Variable assignment ASSIGNMENT_IS_REDEFINE = 1 << 0, diff --git a/shared/public/system.h b/shared/public/system.h index 0e5af118..6e834119 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -140,6 +140,10 @@ namespace ink template void ink_assert( bool condition, const char* msg = nullptr, Args... args ) { + static const char* EMPTY = ""; + if (msg == nullptr) { + msg = EMPTY; + } if ( !condition ) { if constexpr ( sizeof...( args ) > 0 )