Skip to content

Commit 0d6e9d0

Browse files
authored
Synnax v0.52.7 (#2052)
1 parent cdcc373 commit 0d6e9d0

88 files changed

Lines changed: 2257 additions & 523 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

arc/cpp/runtime/constant/constant.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,13 @@ class Constant : public node::Node {
4141
this->initialized = true;
4242
const auto &o = state.output(0);
4343
const auto &o_time = state.output_time(0);
44-
o->resize(1);
44+
if (o->data_type().is_variable())
45+
*o = x::telem::Series(this->value);
46+
else {
47+
o->resize(1);
48+
o->set(0, this->value);
49+
}
4550
o_time->resize(1);
46-
o->set(0, this->value);
4751
o_time->set(0, x::telem::TimeStamp::now());
4852
ctx.mark_changed(ir::default_output_param);
4953
return x::errors::NIL;

arc/cpp/runtime/constant/constant_test.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,38 @@ TEST(ConstantTest, TimestampOutputOnFirstNext) {
241241
EXPECT_GT(output_time->at<int64_t>(0), 0);
242242
}
243243

244+
/// @brief Test that string values are correctly output.
245+
TEST(ConstantTest, StringValueIsOutput) {
246+
const std::string val = "hello";
247+
TestSetup setup(types::Kind::String, val);
248+
Constant node(setup.make_node(), val, x::telem::STRING_T);
249+
250+
auto ctx = make_context();
251+
ASSERT_NIL(node.next(ctx));
252+
253+
auto checker = setup.make_node();
254+
const auto &output = checker.output(0);
255+
EXPECT_EQ(output->size(), 1);
256+
EXPECT_EQ(output->at<std::string>(0), val);
257+
}
258+
259+
/// @brief Test that reset() allows the string value to be output again.
260+
TEST(ConstantTest, StringResetAllowsValueToBeOutputAgain) {
261+
const std::string val = "hello";
262+
TestSetup setup(types::Kind::String, val);
263+
Constant node(setup.make_node(), val, x::telem::STRING_T);
264+
265+
auto ctx = make_context();
266+
node.next(ctx);
267+
node.reset();
268+
ASSERT_NIL(node.next(ctx));
269+
270+
auto checker = setup.make_node();
271+
const auto &output = checker.output(0);
272+
EXPECT_EQ(output->size(), 1);
273+
EXPECT_EQ(output->at<std::string>(0), val);
274+
}
275+
244276
/// @brief Test that reset produces a new timestamp on subsequent next().
245277
TEST(ConstantTest, ResetProducesNewTimestamp) {
246278
TestSetup setup(types::Kind::F32, 42.5f);

arc/cpp/runtime/telem/telem.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,20 @@ class On : public node::Node {
7979
return x::errors::NIL;
8080
}
8181

82+
/// @brief Advances the high water mark to the current channel alignment,
83+
/// ensuring that when a stage is (re-)activated it only responds to
84+
/// data that arrives after activation rather than stale pre-existing data.
85+
void reset() override {
86+
node::Node::reset();
87+
auto [data, index_data, ok] = state.read_chan(channel_key);
88+
if (!ok || data.series.empty()) return;
89+
const auto &last = data.series.back();
90+
const auto lower = last.alignment;
91+
const auto upper_val = lower.uint64() + (last.size() > 0 ? last.size() - 1 : 0);
92+
const auto upper = x::telem::Alignment(upper_val + 1);
93+
if (upper.uint64() > high_water_mark.uint64()) high_water_mark = upper;
94+
}
95+
8296
[[nodiscard]] bool is_output_truthy(const std::string &param_name) const override {
8397
return state.is_output_truthy(param_name);
8498
}

arc/cpp/runtime/telem/telem_test.cpp

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,115 @@ TEST(OnTest, NextSkipsOnAlignmentMismatch) {
431431
EXPECT_EQ(call_count, 0);
432432
}
433433

434+
/// @brief Test that Reset() advances the watermark so stale pre-reset data does not
435+
/// re-trigger the source node, while new data written after reset does.
436+
TEST(OnTest, ResetAdvancesHighWaterMarkPreventingStaleData) {
437+
ir::Param output_param;
438+
output_param.name = ir::default_output_param;
439+
output_param.type = types::Type(types::Kind::F32);
440+
441+
ir::Node ir_node;
442+
ir_node.key = "source";
443+
ir_node.type = "on";
444+
ir_node.outputs.params.push_back(output_param);
445+
446+
ir::Param channel_config;
447+
channel_config.name = "channel";
448+
channel_config.value = static_cast<uint32_t>(10);
449+
ir_node.config.params.push_back(channel_config);
450+
451+
ir::IR ir;
452+
ir.nodes.push_back(ir_node);
453+
454+
state::Config cfg{.ir = ir, .channels = {{10, x::telem::FLOAT32_T, 11}}};
455+
state::State s(cfg, errors::noop_handler);
456+
457+
Factory factory;
458+
auto state_node = ASSERT_NIL_P(s.node("source"));
459+
auto node = ASSERT_NIL_P(
460+
factory.create(node::Config(ir, ir_node, std::move(state_node)))
461+
);
462+
463+
// Ingest data at alignment 0.
464+
x::telem::Frame frame1(2);
465+
auto d1 = x::telem::Series(1.0f);
466+
d1.alignment = x::telem::Alignment(0);
467+
auto t1 = x::telem::Series(static_cast<int64_t>(100));
468+
t1.alignment = x::telem::Alignment(0);
469+
frame1.emplace(10, std::move(d1));
470+
frame1.emplace(11, std::move(t1));
471+
s.ingest(frame1);
472+
473+
// Reset: advances the watermark to 1 (past alignment 0).
474+
node->reset();
475+
476+
// Next should NOT fire: data at alignment 0 is now below the watermark.
477+
bool changed = false;
478+
auto ctx = node::Context{
479+
.elapsed = x::telem::SECOND,
480+
.mark_changed = [&changed](const std::string &) { changed = true; },
481+
.report_error = [](const x::errors::Error &) {},
482+
.activate_stage = [] {},
483+
};
484+
ASSERT_NIL(node->next(ctx));
485+
EXPECT_FALSE(changed) << "stale pre-reset data should not trigger the source";
486+
487+
// Ingest new data at alignment 10 (well above the watermark of 1).
488+
x::telem::Frame frame2(2);
489+
auto d2 = x::telem::Series(2.0f);
490+
d2.alignment = x::telem::Alignment(10);
491+
auto t2 = x::telem::Series(static_cast<int64_t>(200));
492+
t2.alignment = x::telem::Alignment(10);
493+
frame2.emplace(10, std::move(d2));
494+
frame2.emplace(11, std::move(t2));
495+
s.ingest(frame2);
496+
497+
ASSERT_NIL(node->next(ctx));
498+
EXPECT_TRUE(changed) << "data written after reset should trigger the source";
499+
}
500+
501+
/// @brief Test that Reset() is a no-op when the channel has no data.
502+
TEST(OnTest, ResetIsNoOpWithNoData) {
503+
ir::Param output_param;
504+
output_param.name = ir::default_output_param;
505+
output_param.type = types::Type(types::Kind::F32);
506+
507+
ir::Node ir_node;
508+
ir_node.key = "source";
509+
ir_node.type = "on";
510+
ir_node.outputs.params.push_back(output_param);
511+
512+
ir::Param channel_config;
513+
channel_config.name = "channel";
514+
channel_config.value = static_cast<uint32_t>(10);
515+
ir_node.config.params.push_back(channel_config);
516+
517+
ir::IR ir;
518+
ir.nodes.push_back(ir_node);
519+
520+
state::Config cfg{.ir = ir, .channels = {{10, x::telem::FLOAT32_T, 11}}};
521+
state::State s(cfg, errors::noop_handler);
522+
523+
Factory factory;
524+
auto state_node = ASSERT_NIL_P(s.node("source"));
525+
auto node = ASSERT_NIL_P(
526+
factory.create(node::Config(ir, ir_node, std::move(state_node)))
527+
);
528+
529+
// Reset with no ingested data should not crash.
530+
node->reset();
531+
532+
bool changed = false;
533+
auto ctx = node::Context{
534+
.elapsed = x::telem::SECOND,
535+
.mark_changed = [&changed](const std::string &) { changed = true; },
536+
.report_error = [](const x::errors::Error &) {},
537+
.activate_stage = [] {},
538+
};
539+
ASSERT_NIL(node->next(ctx));
540+
EXPECT_FALSE(changed);
541+
}
542+
434543
/// @brief Test source node calls mark_changed callback.
435544
TEST(OnTest, NextCallsMarkChanged) {
436545
ir::Param output_param;

0 commit comments

Comments
 (0)