Skip to content

Commit ec112d3

Browse files
authored
Fix knot tracking (#159)
Re-opening so I can test the workflow, but probably not ready yet. Locally, everything passes (ink-proof, inkcpp-test) except an obscure failure in one of the migration tests while initialising globals (before the migration or anything interesting happens) which I haven't figured out yet.
1 parent 461bc3b commit ec112d3

6 files changed

Lines changed: 168 additions & 24 deletions

File tree

inkcpp/globals_impl.cpp

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,9 @@ globals_impl::globals_impl(const story_impl* story)
3030
}
3131
}
3232

33-
void globals_impl::visit(uint32_t container_id, bool preserve_turns)
33+
void globals_impl::visit(uint32_t container_id)
3434
{
35-
const int32_t existing_turns = _visit_counts[container_id].turns;
36-
_visit_counts.set(
37-
container_id, {_visit_counts[container_id].visits + (preserve_turns ? 0 : 1),
38-
preserve_turns ? existing_turns : 0}
39-
);
35+
_visit_counts.set(container_id, {_visit_counts[container_id].visits + 1, 0});
4036
}
4137

4238
uint32_t globals_impl::visits(uint32_t container_id) const

inkcpp/globals_impl.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,7 @@ class globals_impl final
6565

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

7270
// Checks the number of visits to a container
7371
uint32_t visits(uint32_t container_id) const;

inkcpp/runner_impl.cpp

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -354,8 +354,8 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit, boo
354354
const container_data_t& dest_container = _story->container_data(dest_id);
355355
if (dest_offset == dest_container._start_offset) {
356356
// Record direct jump to non-knot if requested. (Knots handled below.)
357-
if (record_visits && ! dest_container.knot()) {
358-
_globals->visit(dest_id, preserve_turns);
357+
if (! preserve_turns && record_visits && ! dest_container.knot()) {
358+
_globals->visit(dest_id);
359359
}
360360

361361
// Consume instruction so we don't process it again during normal flow. (We need to do this here
@@ -387,8 +387,8 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit, boo
387387
//
388388
// Ink has a rule about incrementing visit counts when you jump to the top of a knot, which
389389
// seems to need to override inkcpp's knot_visit flag.
390-
if (track_knot_visit || container._start_offset == dest_offset) {
391-
_globals->visit(id, preserve_turns);
390+
if (! preserve_turns && (track_knot_visit || container._start_offset == dest_offset)) {
391+
_globals->visit(id);
392392
}
393393

394394
// If tracking, update with the first knot we encounter, which is the one closest to the top
@@ -425,9 +425,15 @@ void runner_impl::start_frame(uint32_t target)
425425
}
426426
_evaluation_mode = false; // unset eval mode when enter function or tunnel
427427

428+
// Always record visits
429+
const bool record_visits = true;
430+
431+
// Do we visit the knot? We need to visit anything that can have knot tags.
432+
const bool track_knot_visit = type != frame_type::function;
433+
428434
// Do the jump
429435
inkAssert(_story->instructions() + target < _story->end(), "Diverting past end of story data!");
430-
jump(_story->instructions() + target, true, false);
436+
jump(_story->instructions() + target, record_visits, track_knot_visit);
431437
}
432438

433439
frame_type runner_impl::execute_return()
@@ -461,13 +467,21 @@ frame_type runner_impl::execute_return()
461467
}
462468
}
463469

470+
// Never record visits
471+
const bool record_visits = false;
472+
473+
// Do we visit the knot? This needs to match what we tracked in start_frame.
474+
const bool track_knot_visit = type != frame_type::function;
475+
476+
// Returns should never update visit counts.
477+
const bool preserve_turns = true;
464478

465479
// Jump to the old offset
466480
inkAssert(
467481
_story->instructions() + offset < _story->end(),
468482
"Callstack return is outside bounds of story!"
469483
);
470-
jump(_story->instructions() + offset, false, false);
484+
jump(_story->instructions() + offset, record_visits, track_knot_visit, preserve_turns);
471485

472486
// Return frame type
473487
return type;
@@ -625,7 +639,11 @@ void runner_impl::choose(size_t index)
625639
inkAssert(prev != nullptr, "No 'done' point recorded before finishing choice output");
626640

627641
// Move to the previous pointer so we track our movements correctly
628-
jump(prev, false, false);
642+
{
643+
const bool record_visits = false;
644+
const bool track_knot_visit = false;
645+
jump(prev, record_visits, track_knot_visit);
646+
}
629647
_done = nullptr;
630648

631649
// Collapse callstacks to the correct thread
@@ -635,7 +653,9 @@ void runner_impl::choose(size_t index)
635653
_eval.clear();
636654

637655
// Jump to destination and clear choice list
638-
jump(_story->instructions() + c.path(), true, false);
656+
const bool record_visits = true;
657+
const bool track_knot_visit = false;
658+
jump(_story->instructions() + c.path(), record_visits, track_knot_visit);
639659
clear_choices();
640660
_entered_knot = false;
641661
}
@@ -815,7 +835,9 @@ bool runner_impl::move_to(hash_t path)
815835
// Clear state and move to destination
816836
reset();
817837
_ptr = _story->instructions();
818-
jump(destination, false, false);
838+
const bool record_visits = false;
839+
const bool track_knot_visit = false;
840+
jump(destination, record_visits, track_knot_visit);
819841

820842
return true;
821843
}
@@ -833,7 +855,8 @@ bool runner_impl::migrate_to(const loader& loader, hash_t path)
833855
ip_t start_of_knot = _story->find_offset_for(_story->container_data(_current_knot_id)._hash);
834856
fetch_tags(start_of_knot);
835857
assign_tags({tags_level::KNOT});
836-
if (start_of_knot != destination) {
858+
// If the destination is in the recorded current knot
859+
if (start_of_knot < destination) {
837860
for (ip_t iter = start_of_knot; iter != destination; iter += 6) {
838861
if (read<Command>(iter) == Command::DEFINE_TEMP) {
839862
hash_t temp_name = read<hash_t>(iter + 2);
@@ -846,7 +869,9 @@ bool runner_impl::migrate_to(const loader& loader, hash_t path)
846869
while (read<Command>(eval_start) != Command::START_EVAL) {
847870
eval_start -= 6;
848871
}
849-
jump(eval_start, false, false);
872+
const bool record_visits = false;
873+
const bool track_knot_visit = false;
874+
jump(eval_start, record_visits, track_knot_visit);
850875
while (_ptr != iter + 6) {
851876
step();
852877
}
@@ -860,7 +885,10 @@ bool runner_impl::migrate_to(const loader& loader, hash_t path)
860885
// without this the visit() call inside jump() would reset them to 0.
861886
_container.clear();
862887
_ptr = nullptr;
863-
jump(destination, false, true, true);
888+
const bool record_visits = false;
889+
const bool track_knot_visit = false;
890+
const bool preserve_turns = true;
891+
jump(destination, record_visits, track_knot_visit, preserve_turns);
864892

865893
if (loader.old_ref_table
866894
&& ! _globals->lists().migrate_variables(
@@ -1202,7 +1230,9 @@ void runner_impl::step()
12021230
inkAssert(
12031231
_story->instructions() + target < _story->end(), "Diverting past end of story data!"
12041232
);
1205-
jump(_story->instructions() + target, true, ! (flag & CommandFlag::DIVERT_HAS_CONDITION));
1233+
const bool record_visits = true;
1234+
const bool track_knot_visit = ! (flag & CommandFlag::DIVERT_HAS_CONDITION);
1235+
jump(_story->instructions() + target, record_visits, track_knot_visit);
12061236
} break;
12071237
case Command::DIVERT_TO_VARIABLE: {
12081238
// Get variable value
@@ -1224,9 +1254,11 @@ void runner_impl::step()
12241254
inkAssert(val, "Jump destiniation needs to be defined!");
12251255

12261256
// Move to location
1257+
const bool record_visits = true;
1258+
const bool track_knot_visit = ! (flag & CommandFlag::DIVERT_HAS_CONDITION);
12271259
jump(
1228-
_story->instructions() + val->get<value_type::divert>(), true,
1229-
! (flag & CommandFlag::DIVERT_HAS_CONDITION)
1260+
_story->instructions() + val->get<value_type::divert>(), record_visits,
1261+
track_knot_visit
12301262
);
12311263
inkAssert(_ptr < _story->end(), "Diverted past end of story data!");
12321264
} break;

inkcpp_test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ add_executable(
1515
Globals.cpp
1616
Lists.cpp
1717
Tags.cpp
18+
TagsAndBranching.cpp
1819
NewLines.cpp
1920
FallbackFunction.cpp
2021
LabelCondition.cpp

inkcpp_test/TagsAndBranching.cpp

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#include "catch.hpp"
2+
#include "system.h"
3+
4+
#include <../runner_impl.h>
5+
#include <choice.h>
6+
#include <compiler.h>
7+
#include <globals.h>
8+
#include <runner.h>
9+
#include <story.h>
10+
11+
using namespace ink::runtime;
12+
13+
SCENARIO("TagsAndBranching", "[tags][branching]")
14+
{
15+
GIVEN("A story with tags and branching")
16+
{
17+
story* _ink = story::from_file(INK_TEST_RESOURCE_DIR "TagsAndBranching.bin");
18+
runner _thread = _ink->new_runner();
19+
20+
WHEN("Starting the thread")
21+
{
22+
CHECK_FALSE(_thread->has_tags());
23+
CHECK_FALSE(_thread->has_knot_tags());
24+
CHECK(_thread->get_current_knot() == 0);
25+
}
26+
27+
WHEN("On the plain text line")
28+
{
29+
CHECK(_thread->getline() == "Plain text\n");
30+
THEN("It has tags")
31+
{
32+
CHECK(! _thread->has_knot_tags());
33+
CHECK(_thread->has_tags());
34+
REQUIRE(_thread->num_tags() == 1);
35+
REQUIRE(std::string(_thread->get_tag(0)) == "plain_text_tag");
36+
}
37+
}
38+
39+
WHEN("In the knot")
40+
{
41+
// Skip previous test
42+
_thread->getline();
43+
CHECK(_thread->getline() == "Knot text\n");
44+
THEN("It has tags")
45+
{
46+
CHECK(_thread->get_current_knot() == ink::hash_string("Knot"));
47+
CHECK(_thread->has_knot_tags());
48+
REQUIRE(_thread->num_knot_tags() == 1);
49+
REQUIRE(std::string(_thread->get_knot_tag(0)) == "knot_tag");
50+
CHECK(_thread->has_tags());
51+
REQUIRE(_thread->num_tags() == 2);
52+
REQUIRE(std::string(_thread->get_tag(0)) == "knot_tag");
53+
REQUIRE(std::string(_thread->get_tag(1)) == "knot_text_tag");
54+
}
55+
}
56+
57+
WHEN("In the tunnel")
58+
{
59+
// Skip previous tests
60+
_thread->getline();
61+
_thread->getline();
62+
CHECK(_thread->getline() == "Tunnel text\n");
63+
THEN("It has tags")
64+
{
65+
CHECK(_thread->has_knot_tags());
66+
REQUIRE(_thread->num_knot_tags() == 1);
67+
REQUIRE(std::string(_thread->get_knot_tag(0)) == "tunnel_tag");
68+
CHECK(_thread->has_tags());
69+
REQUIRE(_thread->num_tags() == 2);
70+
REQUIRE(std::string(_thread->get_tag(0)) == "tunnel_tag");
71+
REQUIRE(std::string(_thread->get_tag(1)) == "tunnel_text_tag");
72+
}
73+
}
74+
75+
WHEN("In the thread")
76+
{
77+
// Skip previous tests
78+
_thread->getline();
79+
_thread->getline();
80+
_thread->getline();
81+
CHECK(_thread->getline() == "Thread text\n");
82+
THEN("It has tags")
83+
{
84+
CHECK(_thread->has_knot_tags());
85+
REQUIRE(_thread->num_knot_tags() == 1);
86+
REQUIRE(std::string(_thread->get_knot_tag(0)) == "thread_tag");
87+
CHECK(_thread->has_tags());
88+
REQUIRE(_thread->num_tags() == 2);
89+
REQUIRE(std::string(_thread->get_tag(0)) == "thread_tag");
90+
REQUIRE(std::string(_thread->get_tag(1)) == "thread_text_tag");
91+
}
92+
}
93+
}
94+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Plain text # plain_text_tag
2+
->Knot
3+
4+
=== Continue
5+
->Tunnel->
6+
<-Thread
7+
->DONE
8+
9+
// All these knots should be tracked for tagging and visit counts
10+
=== Knot
11+
#knot_tag
12+
Knot text #knot_text_tag
13+
->Continue
14+
15+
=== Tunnel
16+
#tunnel_tag
17+
Tunnel text #tunnel_text_tag
18+
->->
19+
20+
=== Thread
21+
#thread_tag
22+
Thread text #thread_text_tag
23+
->DONE

0 commit comments

Comments
 (0)