diff --git a/arc/go/analyzer/expression/expression_test.go b/arc/go/analyzer/expression/expression_test.go index 8ac41cc215..37352986b9 100644 --- a/arc/go/analyzer/expression/expression_test.go +++ b/arc/go/analyzer/expression/expression_test.go @@ -934,11 +934,13 @@ var _ = Describe("Expressions", func() { Kind: symbol.KindChannel, Name: "ox_pt_1", Type: types.Chan(types.I32()), + ID: 20001, }, "ox_pt_2": symbol.Symbol{ Kind: symbol.KindChannel, Name: "ox_pt_2", Type: types.Chan(types.I32()), + ID: 20002, }, } ctx := context.CreateRoot(bCtx, ast, resolver) @@ -957,11 +959,13 @@ var _ = Describe("Expressions", func() { Kind: symbol.KindChannel, Name: "ox_pt_1", Type: types.Chan(types.I32()), + ID: 20003, }, "ox_pt_2": symbol.Symbol{ Kind: symbol.KindChannel, Name: "ox_pt_2", Type: types.Chan(types.F32()), + ID: 20004, }, } ctx := context.CreateRoot(bCtx, ast, resolver) @@ -982,6 +986,7 @@ var _ = Describe("Expressions", func() { Kind: symbol.KindChannel, Name: "ox_pt_1", Type: types.Chan(types.I32()), + ID: 20005, }, } ctx := context.CreateRoot(bCtx, ast, resolver) @@ -1002,6 +1007,7 @@ var _ = Describe("Expressions", func() { Kind: symbol.KindChannel, Name: "sensor", Type: types.Chan(types.F32()), + ID: 20006, }, }), Entry("comparison", ` @@ -1013,6 +1019,7 @@ var _ = Describe("Expressions", func() { Kind: symbol.KindChannel, Name: "sensor", Type: types.Chan(types.F32()), + ID: 20007, }, }), Entry("multiple channels in expression", ` @@ -1020,9 +1027,9 @@ var _ = Describe("Expressions", func() { return (temp1 + temp2 + temp3) / 3 } `, symbol.MapResolver{ - "temp1": symbol.Symbol{Kind: symbol.KindChannel, Name: "temp1", Type: types.Chan(types.F64())}, - "temp2": symbol.Symbol{Kind: symbol.KindChannel, Name: "temp2", Type: types.Chan(types.F64())}, - "temp3": symbol.Symbol{Kind: symbol.KindChannel, Name: "temp3", Type: types.Chan(types.F64())}, + "temp1": symbol.Symbol{Kind: symbol.KindChannel, Name: "temp1", Type: types.Chan(types.F64()), ID: 20008}, + "temp2": symbol.Symbol{Kind: symbol.KindChannel, Name: "temp2", Type: types.Chan(types.F64()), ID: 20009}, + "temp3": symbol.Symbol{Kind: symbol.KindChannel, Name: "temp3", Type: types.Chan(types.F64()), ID: 20010}, }), ) @@ -1032,6 +1039,7 @@ var _ = Describe("Expressions", func() { Kind: symbol.KindChannel, Name: "sensor", Type: types.Chan(types.F32()), + ID: 20011, }, } expectFailure(` diff --git a/arc/go/analyzer/statement/statement.go b/arc/go/analyzer/statement/statement.go index d65575ead4..9296a0d5d9 100644 --- a/arc/go/analyzer/statement/statement.go +++ b/arc/go/analyzer/statement/statement.go @@ -240,6 +240,11 @@ func analyzeStatefulVariable(ctx context.Context[parser.IStatefulVariableContext }) return } + // Stateful variables store VALUES, not channel references. + // If initialized from a channel, unwrap to get the value type. + if varType.Kind == types.KindChan { + varType = varType.Unwrap() + } _, err := ctx.Scope.Add(ctx, symbol.Symbol{ Name: name, Kind: symbol.KindStatefulVariable, diff --git a/arc/go/runtime/wasm/wasm_test.go b/arc/go/runtime/wasm/wasm_test.go index ba9e6e61c1..90c56a0f3f 100644 --- a/arc/go/runtime/wasm/wasm_test.go +++ b/arc/go/runtime/wasm/wasm_test.go @@ -2542,5 +2542,148 @@ input_ch -> checker{} -> output_ch result = h.Output("checker_0", 0) Expect(telem.UnmarshalSeries[uint8](result)[0]).To(Equal(uint8(0))) }) + + It("Should write to separate channels when function with channel config is used multiple times", func() { + resolver := symbol.MapResolver{ + "input_1": { + Name: "input_1", + Kind: symbol.KindChannel, + Type: types.Chan(types.U8()), + ID: 101, + }, + "input_2": { + Name: "input_2", + Kind: symbol.KindChannel, + Type: types.Chan(types.U8()), + ID: 102, + }, + "counter_1": { + Name: "counter_1", + Kind: symbol.KindChannel, + Type: types.Chan(types.F32()), + ID: 201, + }, + "counter_2": { + Name: "counter_2", + Kind: symbol.KindChannel, + Type: types.Chan(types.F32()), + ID: 202, + }, + "sink_1": { + Name: "sink_1", + Kind: symbol.KindChannel, + Type: types.Chan(types.U8()), + ID: 301, + }, + "sink_2": { + Name: "sink_2", + Kind: symbol.KindChannel, + Type: types.Chan(types.U8()), + ID: 302, + }, + } + + source := ` +func increment{ + counter chan f32 +} (trigger u8) u8 { + if trigger != 0 { + counter = counter + 1.0 + } + return 0 +} + +input_1 -> increment{counter=counter_1} -> sink_1 +input_2 -> increment{counter=counter_2} -> sink_2 +` + h := newTextHarness(ctx, source, resolver, []state.ChannelDigest{ + {Key: 101, DataType: telem.Uint8T}, + {Key: 102, DataType: telem.Uint8T}, + {Key: 201, DataType: telem.Float32T}, + {Key: 202, DataType: telem.Float32T}, + {Key: 301, DataType: telem.Uint8T}, + {Key: 302, DataType: telem.Uint8T}, + }) + defer h.Close() + + fr := telem.Frame[uint32]{} + fr = fr.Append(201, telem.NewSeriesV[float32](0.0)) + fr = fr.Append(202, telem.NewSeriesV[float32](0.0)) + h.state.Ingest(fr) + + h.SetInput("on_input_1_0", 0, telem.NewSeriesV[uint8](1), telem.NewSeriesSecondsTSV(1)) + h.SetInput("on_input_2_0", 0, telem.NewSeriesV[uint8](1), telem.NewSeriesSecondsTSV(1)) + h.Execute(ctx, "increment_0") + h.Execute(ctx, "increment_1") + + outFr, changed := h.state.Flush(telem.Frame[uint32]{}) + Expect(changed).To(BeTrue()) + Expect(outFr.Get(201).Series).To(HaveLen(1), "counter_1 should have been written") + Expect(telem.UnmarshalSeries[float32](outFr.Get(201).Series[0])[0]).To(Equal(float32(1.0))) + Expect(outFr.Get(202).Series).To(HaveLen(1), "counter_2 should have been written") + Expect(telem.UnmarshalSeries[float32](outFr.Get(202).Series[0])[0]).To(Equal(float32(1.0))) + }) + + It("Should not write to channel when stateful variable initialized from channel is modified", func() { + resolver := symbol.MapResolver{ + "input_ch": { + Name: "input_ch", + Kind: symbol.KindChannel, + Type: types.Chan(types.U8()), + ID: 100, + }, + "counter_ch": { + Name: "counter_ch", + Kind: symbol.KindChannel, + Type: types.Chan(types.F32()), + ID: 200, + }, + "sink_ch": { + Name: "sink_ch", + Kind: symbol.KindChannel, + Type: types.Chan(types.U8()), + ID: 300, + }, + } + + source := ` +func count_local (trigger u8) u8 { + counter $= counter_ch + prev $= trigger + if trigger != 0 and prev == 0 { + counter = counter + 1.0 + } + prev = trigger + return 0 +} + +input_ch -> count_local{} -> sink_ch +` + h := newTextHarness(ctx, source, resolver, []state.ChannelDigest{ + {Key: 100, DataType: telem.Uint8T}, + {Key: 200, DataType: telem.Float32T}, + {Key: 300, DataType: telem.Uint8T}, + }) + defer h.Close() + + fr := telem.Frame[uint32]{} + fr = fr.Append(200, telem.NewSeriesV[float32](5.0)) + h.state.Ingest(fr) + + h.SetInput("on_input_ch_0", 0, telem.NewSeriesV[uint8](0), telem.NewSeriesSecondsTSV(1)) + h.Execute(ctx, "count_local_0") + + outFr, _ := h.state.Flush(telem.Frame[uint32]{}) + Expect(outFr.Get(200).Series).To(BeEmpty()) + + fr = telem.Frame[uint32]{} + fr = fr.Append(200, telem.NewSeriesV[float32](5.0)) + h.state.Ingest(fr) + h.SetInput("on_input_ch_0", 0, telem.NewSeriesV[uint8](1), telem.NewSeriesSecondsTSV(2)) + h.Execute(ctx, "count_local_0") + + outFr, _ = h.state.Flush(telem.Frame[uint32]{}) + Expect(outFr.Get(200).Series).To(BeEmpty()) + }) }) }) diff --git a/arc/go/text/analyze.go b/arc/go/text/analyze.go index e82428d73e..fc3f96b9d7 100644 --- a/arc/go/text/analyze.go +++ b/arc/go/text/analyze.go @@ -236,7 +236,7 @@ func analyzeFunctionNode( n := ir.Node{ Key: key, Type: name, - Channels: sym.Channels, + Channels: sym.Channels.Copy(), Config: slices.Clone(sym.Type.Config), Outputs: slices.Clone(sym.Type.Outputs), Inputs: slices.Clone(sym.Type.Inputs), diff --git a/arc/go/text/text_test.go b/arc/go/text/text_test.go index d69332939f..f6bd2ff218 100644 --- a/arc/go/text/text_test.go +++ b/arc/go/text/text_test.go @@ -143,7 +143,7 @@ var _ = Describe("Text", func() { Context("Channel Flow Analysis", func() { It("Should analyze flow with channel identifier", func() { resolver := symbol.MapResolver{ - "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.I32()), ID: 42}, + "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.I32()), ID: 10042}, } source := ` func print{} () { @@ -163,7 +163,7 @@ var _ = Describe("Text", func() { Expect(channelNode.Config).To(HaveLen(1)) Expect(channelNode.Config[0].Name).To(Equal("channel")) Expect(channelNode.Config[0].Type).To(Equal(types.Chan(types.I32()))) - Expect(channelNode.Channels.Read.Contains(42)).To(BeTrue()) + Expect(channelNode.Channels.Read.Contains(10042)).To(BeTrue()) printNode := findNodeByKey(inter.Nodes, "print_0") Expect(printNode.Type).To(Equal("print")) @@ -236,17 +236,17 @@ var _ = Describe("Text", func() { }, Entry("integer literal", `1 -> output`, - symbol.MapResolver{"output": {Name: "output", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 1}}, + symbol.MapResolver{"output": {Name: "output", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10001}}, true, types.F32(), ), Entry("float literal", `3.14 -> output`, - symbol.MapResolver{"output": {Name: "output", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 1}}, + symbol.MapResolver{"output": {Name: "output", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 10002}}, true, types.F64(), ), Entry("complex expression (should not generate constant)", `1 + 2 -> output`, - symbol.MapResolver{"output": {Name: "output", Kind: symbol.KindChannel, Type: types.Chan(types.I64()), ID: 1}}, + symbol.MapResolver{"output": {Name: "output", Kind: symbol.KindChannel, Type: types.Chan(types.I64()), ID: 10003}}, false, types.Type{}, // Type ignored when expectConstant is false ), ) @@ -317,7 +317,7 @@ var _ = Describe("Text", func() { It("Should resolve channel name to channel ID in config parameter", func() { resolver := symbol.MapResolver{ - "temp_sensor": {Name: "temp_sensor", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 42}, + "temp_sensor": {Name: "temp_sensor", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 10042}, } source := ` func reader{ @@ -339,13 +339,13 @@ var _ = Describe("Text", func() { Expect(readerNode.Config).To(HaveLen(1)) Expect(readerNode.Config[0].Name).To(Equal("channel")) Expect(readerNode.Config[0].Type).To(Equal(types.Chan(types.F64()))) - Expect(readerNode.Config[0].Value).To(Equal(uint32(42))) - Expect(readerNode.Channels.Read.Contains(uint32(42))).To(BeTrue()) + Expect(readerNode.Config[0].Value).To(Equal(uint32(10042))) + Expect(readerNode.Channels.Read.Contains(uint32(10042))).To(BeTrue()) }) It("Should produce diagnostic error when channel config type mismatches", func() { resolver := symbol.MapResolver{ - "temp_sensor": {Name: "temp_sensor", Kind: symbol.KindChannel, Type: types.Chan(types.I32()), ID: 42}, + "temp_sensor": {Name: "temp_sensor", Kind: symbol.KindChannel, Type: types.Chan(types.I32()), ID: 10043}, } source := ` func reader{ @@ -393,7 +393,7 @@ var _ = Describe("Text", func() { It("Should resolve channel name for write operations and add to Channels.Write", func() { resolver := symbol.MapResolver{ - "output_channel": {Name: "output_channel", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 55}, + "output_channel": {Name: "output_channel", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 10055}, } source := ` func writer{ @@ -416,16 +416,81 @@ var _ = Describe("Text", func() { Expect(writerNode.Config).To(HaveLen(1)) Expect(writerNode.Config[0].Name).To(Equal("channel")) Expect(writerNode.Config[0].Type).To(Equal(types.Chan(types.F64()))) - Expect(writerNode.Config[0].Value).To(Equal(uint32(55))) - Expect(writerNode.Channels.Write.Contains(uint32(55))).To(BeTrue()) - Expect(writerNode.Channels.Read.Contains(uint32(55))).To(BeFalse()) + Expect(writerNode.Config[0].Value).To(Equal(uint32(10055))) + Expect(writerNode.Channels.Write.Contains(uint32(10055))).To(BeTrue()) + Expect(writerNode.Channels.Read.Contains(uint32(10055))).To(BeFalse()) + }) + + It("Should register separate write channels when function with channel config is used multiple times", func() { + resolver := symbol.MapResolver{ + "toggle_1": {Name: "toggle_1", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 10011}, + "toggle_2": {Name: "toggle_2", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 10012}, + "counter_1": {Name: "counter_1", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10013}, + "counter_2": {Name: "counter_2", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10014}, + } + source := ` + func count_rising{counter chan f32}(input u8) { + prev $= input + if input != 0 and prev == 0 { + counter = counter + 1.0 + } + prev = input + } + + toggle_1 -> count_rising{counter=counter_1} + toggle_2 -> count_rising{counter=counter_2} + ` + parsedText := MustSucceed(text.Parse(text.Text{Raw: source})) + inter, diagnostics := text.Analyze(ctx, parsedText, resolver) + Expect(diagnostics.Ok()).To(BeTrue(), diagnostics.String()) + + // Find the two count_rising nodes + node1 := findNodeByKey(inter.Nodes, "count_rising_0") + node2 := findNodeByKey(inter.Nodes, "count_rising_1") + + // Each node should have its own write channel + Expect(node1.Channels.Write.Contains(uint32(10013))).To(BeTrue(), "first node should write to counter_1") + Expect(node2.Channels.Write.Contains(uint32(10014))).To(BeTrue(), "second node should write to counter_2") + + Expect(node1.Channels.Read.Contains(uint32(10013))).To(BeTrue(), "first node should read from counter_1") + Expect(node2.Channels.Read.Contains(uint32(10014))).To(BeTrue(), "second node should read from counter_2") + }) + + It("Should not add stateful variable to write channels when initialized from global channel", func() { + resolver := symbol.MapResolver{ + "toggle_1": {Name: "toggle_1", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 10101}, + "counter_1": {Name: "counter_1", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10102}, + } + source := ` + func count_rising(input u8) { + counter $= counter_1 + prev $= input + if input != 0 and prev == 0 { + counter = counter + 1.0 + } + prev = input + } + + toggle_1 -> count_rising{} + ` + parsedText := MustSucceed(text.Parse(text.Text{Raw: source})) + inter, diagnostics := text.Analyze(ctx, parsedText, resolver) + Expect(diagnostics.Ok()).To(BeTrue(), diagnostics.String()) + + // Find the count_rising node + node := findNodeByKey(inter.Nodes, "count_rising_0") + + // counter_1 should be in Read (stateful var is initialized from channel value) + Expect(node.Channels.Read.Contains(uint32(10102))).To(BeTrue(), "should read from counter_1") + // Write channels should be empty - we write to a stateful variable, not a channel + Expect(node.Channels.Write).To(BeEmpty(), "should not have any write channels") }) }) Context("Edge Parameter Validation", func() { It("Should create edges with parameters that exist in node definitions", func() { resolver := symbol.MapResolver{ - "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.I64()), ID: 1}, + "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.I64()), ID: 10001}, } source := ` func filter{} (data i64) i64 { @@ -502,7 +567,7 @@ var _ = Describe("Text", func() { It("Should verify channel node outputs are defined", func() { resolver := symbol.MapResolver{ - "temp": {Name: "temp", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 42}, + "temp": {Name: "temp", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 10044}, } source := ` func display{} (value f64) { @@ -664,7 +729,7 @@ var _ = Describe("Text", func() { Context("Stratification", func() { It("Should calculate strata for simple flow chain", func() { resolver := symbol.MapResolver{ - "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.I64()), ID: 1}, + "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.I64()), ID: 10001}, } source := ` func filter{} (data i64) i64 { @@ -727,8 +792,8 @@ var _ = Describe("Text", func() { Context("Channel Sink Detection", func() { It("Should create write node for channel at end of flow", func() { resolver := symbol.MapResolver{ - "input_chan": {Name: "input_chan", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 1}, - "output_chan": {Name: "output_chan", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 2}, + "input_chan": {Name: "input_chan", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10021}, + "output_chan": {Name: "output_chan", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10022}, } source := ` func double{} (x f32) f32 { @@ -745,12 +810,12 @@ var _ = Describe("Text", func() { inputNode := inter.Nodes[0] Expect(inputNode.Type).To(Equal("on")) - Expect(inputNode.Channels.Read.Contains(uint32(1))).To(BeTrue()) + Expect(inputNode.Channels.Read.Contains(uint32(10021))).To(BeTrue()) Expect(inputNode.Outputs).To(HaveLen(1)) outputNode := inter.Nodes[2] Expect(outputNode.Type).To(Equal("write")) - Expect(outputNode.Channels.Write.Contains(uint32(2))).To(BeTrue()) + Expect(outputNode.Channels.Write.Contains(uint32(10022))).To(BeTrue()) Expect(outputNode.Inputs).To(HaveLen(1)) Expect(outputNode.Inputs[0].Name).To(Equal("input")) Expect(outputNode.Outputs).To(BeEmpty()) @@ -758,8 +823,8 @@ var _ = Describe("Text", func() { It("Should handle channel-to-channel flow", func() { resolver := symbol.MapResolver{ - "chan1": {Name: "chan1", Kind: symbol.KindChannel, Type: types.Chan(types.I32()), ID: 1}, - "chan2": {Name: "chan2", Kind: symbol.KindChannel, Type: types.Chan(types.I32()), ID: 2}, + "chan1": {Name: "chan1", Kind: symbol.KindChannel, Type: types.Chan(types.I32()), ID: 10031}, + "chan2": {Name: "chan2", Kind: symbol.KindChannel, Type: types.Chan(types.I32()), ID: 10032}, } source := `chan1 -> chan2` parsedText := MustSucceed(text.Parse(text.Text{Raw: source})) @@ -768,15 +833,15 @@ var _ = Describe("Text", func() { Expect(inter.Nodes).To(HaveLen(2)) Expect(inter.Nodes[0].Type).To(Equal("on")) - Expect(inter.Nodes[0].Channels.Read.Contains(uint32(1))).To(BeTrue()) + Expect(inter.Nodes[0].Channels.Read.Contains(uint32(10031))).To(BeTrue()) Expect(inter.Nodes[1].Type).To(Equal("write")) - Expect(inter.Nodes[1].Channels.Write.Contains(uint32(2))).To(BeTrue()) + Expect(inter.Nodes[1].Channels.Write.Contains(uint32(10032))).To(BeTrue()) }) It("Should handle channel sinks in routing tables", func() { resolver := symbol.MapResolver{ - "high_chan": {Name: "high_chan", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 1}, - "low_chan": {Name: "low_chan", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 2}, + "high_chan": {Name: "high_chan", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 10041}, + "low_chan": {Name: "low_chan", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 10045}, } source := ` func demux{threshold f64} (value f64) (high f64, low f64) { @@ -829,7 +894,7 @@ var _ = Describe("Text", func() { Context("Sequence Targeting", func() { It("Should connect one-shot edge to sequence's first stage entry node", func() { resolver := symbol.MapResolver{ - "trigger": {Name: "trigger", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 1}, + "trigger": {Name: "trigger", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 10051}, } source := ` sequence main { @@ -869,7 +934,7 @@ var _ = Describe("Text", func() { It("Should handle continuous flow to sequence", func() { resolver := symbol.MapResolver{ - "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 1}, + "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 10061}, } source := ` sequence main { @@ -892,7 +957,7 @@ var _ = Describe("Text", func() { It("Should handle sequence with multiple stages - connects to first stage", func() { resolver := symbol.MapResolver{ - "trigger": {Name: "trigger", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 1}, + "trigger": {Name: "trigger", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 10051}, } source := ` sequence main { @@ -918,7 +983,7 @@ var _ = Describe("Text", func() { It("Should error when targeting empty sequence (no stages)", func() { resolver := symbol.MapResolver{ - "trigger": {Name: "trigger", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 1}, + "trigger": {Name: "trigger", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 10051}, } source := ` sequence empty { @@ -934,7 +999,7 @@ var _ = Describe("Text", func() { It("Should handle sequence in routing table as sink", func() { resolver := symbol.MapResolver{ - "high_chan": {Name: "high_chan", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 1}, + "high_chan": {Name: "high_chan", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 10071}, } source := ` sequence alarm { @@ -965,7 +1030,7 @@ var _ = Describe("Text", func() { Expect(entryNode.Type).To(Equal("stage_entry")) writeNode := findNodeByType(inter.Nodes, "write") - Expect(writeNode.Channels.Write.Contains(uint32(1))).To(BeTrue()) + Expect(writeNode.Channels.Write.Contains(uint32(10071))).To(BeTrue()) Expect(inter.Edges).To(HaveLen(2)) @@ -978,7 +1043,7 @@ var _ = Describe("Text", func() { Context("Direct Stage Targeting", func() { It("Should allow targeting a stage by name within a sequence", func() { resolver := symbol.MapResolver{ - "input": {Name: "input", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 1}, + "input": {Name: "input", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10081}, } source := ` sequence main { @@ -1012,8 +1077,8 @@ var _ = Describe("Text", func() { } }` resolver := symbol.MapResolver{ - "input": {Name: "input", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 1}, - "output": {Name: "output", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 2}, + "input": {Name: "input", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10091}, + "output": {Name: "output", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 10092}, } parsedText := MustSucceed(text.Parse(text.Text{Raw: source})) inter, diag := text.Analyze(ctx, parsedText, resolver) @@ -1040,14 +1105,14 @@ var _ = Describe("Text", func() { } }`, symbol.MapResolver{ - "input": {Name: "input", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 1}, + "input": {Name: "input", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10081}, }, "no next stage", ), Entry("next outside sequence", `input > 10 => next`, symbol.MapResolver{ - "input": {Name: "input", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 1}, + "input": {Name: "input", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10081}, }, "outside of a sequence", ), @@ -1057,7 +1122,7 @@ var _ = Describe("Text", func() { Context("Implicit Expression Triggers", func() { It("Should inject implicit trigger for expression as first flow node", func() { resolver := symbol.MapResolver{ - "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 42}, + "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10142}, } source := ` func alarm{} (value u8) { @@ -1073,11 +1138,11 @@ var _ = Describe("Text", func() { triggerNode := findNodeByKey(inter.Nodes, "on_sensor_0") Expect(triggerNode.Type).To(Equal("on")) - Expect(triggerNode.Channels.Read.Contains(uint32(42))).To(BeTrue()) + Expect(triggerNode.Channels.Read.Contains(uint32(10142))).To(BeTrue()) exprNode := inter.Nodes[1] Expect(exprNode.Type).To(HavePrefix("expression_")) - Expect(exprNode.Channels.Read.Contains(uint32(42))).To(BeTrue()) + Expect(exprNode.Channels.Read.Contains(uint32(10142))).To(BeTrue()) Expect(inter.Edges).To(HaveLen(2)) @@ -1095,8 +1160,8 @@ var _ = Describe("Text", func() { It("Should inject multiple triggers for multi-channel expression", func() { resolver := symbol.MapResolver{ - "temp": {Name: "temp", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 1}, - "pressure": {Name: "pressure", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 2}, + "temp": {Name: "temp", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10151}, + "pressure": {Name: "pressure", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10152}, } source := ` func alarm{} (value u8) { @@ -1121,8 +1186,8 @@ var _ = Describe("Text", func() { } } Expect(exprNode.Channels.Read).To(HaveLen(2)) - Expect(exprNode.Channels.Read.Contains(uint32(1))).To(BeTrue()) - Expect(exprNode.Channels.Read.Contains(uint32(2))).To(BeTrue()) + Expect(exprNode.Channels.Read.Contains(uint32(10151))).To(BeTrue()) + Expect(exprNode.Channels.Read.Contains(uint32(10152))).To(BeTrue()) Expect(inter.Edges).To(HaveLen(3)) @@ -1138,7 +1203,7 @@ var _ = Describe("Text", func() { It("Should not inject trigger for constant expressions", func() { resolver := symbol.MapResolver{ - "output": {Name: "output", Kind: symbol.KindChannel, Type: types.Chan(types.I64()), ID: 1}, + "output": {Name: "output", Kind: symbol.KindChannel, Type: types.Chan(types.I64()), ID: 10161}, } source := `1 + 2 -> output` parsedText := MustSucceed(text.Parse(text.Text{Raw: source})) @@ -1153,7 +1218,7 @@ var _ = Describe("Text", func() { It("Should not inject trigger when expression is not first node", func() { resolver := symbol.MapResolver{ - "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 42}, + "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10142}, } source := ` func alarm{} (value u8) { @@ -1175,7 +1240,7 @@ var _ = Describe("Text", func() { It("Should inject trigger for expression in sequence stage", func() { resolver := symbol.MapResolver{ - "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 42}, + "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10142}, } source := ` sequence main { @@ -1194,7 +1259,7 @@ var _ = Describe("Text", func() { Expect(triggerCount).To(Equal(1)) triggerNode := findNodeByType(inter.Nodes, "on") - Expect(triggerNode.Channels.Read.Contains(uint32(42))).To(BeTrue()) + Expect(triggerNode.Channels.Read.Contains(uint32(10142))).To(BeTrue()) }) }) @@ -1389,7 +1454,7 @@ var _ = Describe("Text", func() { Name: "virt_1", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), - ID: 25, + ID: 10025, }, } source := ` @@ -1423,7 +1488,7 @@ var _ = Describe("Text", func() { Expect(diagnostics.Ok()).To(BeTrue(), diagnostics.String()) Expect(ir.Nodes).To(HaveLen(3)) Expect(ir.Nodes[1].Channels.Read).To(HaveLen(1)) - Expect(ir.Nodes[1].Channels.Read.Contains(25)).To(BeTrue()) + Expect(ir.Nodes[1].Channels.Read.Contains(10025)).To(BeTrue()) module := MustSucceed(text.Compile(ctx, ir)) Expect(module.Output.WASM).ToNot(BeEmpty()) @@ -1439,19 +1504,19 @@ var _ = Describe("Text", func() { Name: "input_ch", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), - ID: 100, + ID: 10100, }, "write_target": { Name: "write_target", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), - ID: 200, + ID: 10200, }, "sink_ch": { Name: "sink_ch", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), - ID: 300, + ID: 10300, }, } source := ` @@ -1475,7 +1540,7 @@ var _ = Describe("Text", func() { writerNode := ir.Nodes[1] Expect(writerNode.Type).To(Equal("writer")) Expect(writerNode.Channels.Write).To(HaveLen(1)) - Expect(writerNode.Channels.Write.Contains(200)).To(BeTrue()) + Expect(writerNode.Channels.Write.Contains(10200)).To(BeTrue()) module := MustSucceed(text.Compile(ctx, ir)) Expect(module.Output.WASM).ToNot(BeEmpty()) @@ -1490,19 +1555,19 @@ var _ = Describe("Text", func() { Name: "input_ch", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), - ID: 100, + ID: 10110, }, "output_ch": { Name: "output_ch", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), - ID: 200, + ID: 10210, }, "sink_ch": { Name: "sink_ch", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), - ID: 300, + ID: 10310, }, } source := ` @@ -1519,12 +1584,10 @@ var _ = Describe("Text", func() { Expect(diagnostics.Ok()).To(BeTrue(), diagnostics.String()) Expect(ir.Nodes).To(HaveLen(3)) - // The writer function node should have output_ch (200) in Channels.Write - // NOT the intermediate variable's ID writerNode := ir.Nodes[1] Expect(writerNode.Type).To(Equal("writer")) Expect(writerNode.Channels.Write).To(HaveLen(1)) - Expect(writerNode.Channels.Write.Contains(200)).To(BeTrue()) + Expect(writerNode.Channels.Write.Contains(10210)).To(BeTrue()) module := MustSucceed(text.Compile(ctx, ir)) Expect(module.Output.WASM).ToNot(BeEmpty()) diff --git a/console/src/arc/editor/Controls.css b/console/src/arc/editor/Controls.css index 38342385d1..729417700b 100644 --- a/console/src/arc/editor/Controls.css +++ b/console/src/arc/editor/Controls.css @@ -10,6 +10,8 @@ */ .console-arc-editor { + container-type: inline-size; + .console-editor { padding-bottom: 11rem; @@ -32,7 +34,15 @@ box-shadow: var(--pluto-shadow-v2); & .console-rack-select { - min-width: 225px; + flex-grow: 1; + } + } +} + +@container (max-width: 400px) { + .console-arc-editor { + .console-rack-select { + display: none; } } } diff --git a/console/src/hardware/common/task/controls/Controls.css b/console/src/hardware/common/task/controls/Controls.css index 1be404f249..c8cdf5101c 100644 --- a/console/src/hardware/common/task/controls/Controls.css +++ b/console/src/hardware/common/task/controls/Controls.css @@ -79,6 +79,23 @@ @container (max-width: 600px) { .console-task-controls { flex-direction: column !important; + overflow: hidden; + + .console-task-status__time-stamp { + display: none; + } + + .console-task-controls__actions { + width: 100%; + } + + .console-task-status__copy-button { + font-size: 0; + gap: 0; + & svg { + font-size: 2rem; + } + } .console-task-status { min-height: 10rem; diff --git a/console/src/hardware/common/task/controls/Status.tsx b/console/src/hardware/common/task/controls/Status.tsx index bf840c8260..6d54286675 100644 --- a/console/src/hardware/common/task/controls/Status.tsx +++ b/console/src/hardware/common/task/controls/Status.tsx @@ -66,12 +66,19 @@ export const Status = ({ {message} - - - {stat.time} - - {expanded && ( + {expanded && ( + + + {stat.time} + Copy diagnostics - )} - + + )} {expanded && hasDescription && ( diff --git a/core/pkg/service/arc/runtime/task.go b/core/pkg/service/arc/runtime/task.go index dd64650b77..a1303245eb 100644 --- a/core/pkg/service/arc/runtime/task.go +++ b/core/pkg/service/arc/runtime/task.go @@ -281,6 +281,7 @@ func (d *dataRuntime) next( d.scheduler.Next(ctx, telem.Since(d.startTime), reason) d.state.ClearReads() if fr, changed := d.state.Flush(telem.Frame[uint32]{}); changed && d.Out != nil { + fmt.Println(fr) req := framer.WriterRequest{ Frame: frame.NewFromStorage(fr), Command: writer.CommandWrite,