Implemented admin pane + user permission
This commit is contained in:
@@ -49,6 +49,17 @@ type NodeDef struct {
|
||||
type Graph struct {
|
||||
Nodes []GraphNode `json:"nodes"`
|
||||
Output string `json:"output"` // id of the output node
|
||||
// Groups is cosmetic editor metadata (node grouping/collapsing). It has no
|
||||
// effect on evaluation; stored and round-tripped so the editor keeps shape.
|
||||
Groups []NodeGroup `json:"groups,omitempty"`
|
||||
}
|
||||
|
||||
// NodeGroup is a cosmetic, editor-side grouping of graph nodes (no eval effect).
|
||||
type NodeGroup struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label,omitempty"`
|
||||
Members []string `json:"members"`
|
||||
Collapsed bool `json:"collapsed,omitempty"`
|
||||
}
|
||||
|
||||
// GraphNode is one node in a Graph.
|
||||
|
||||
@@ -52,7 +52,20 @@ func (rg *runtimeGraph) sourceRefs() []broker.SignalRef {
|
||||
// - stateful ops (filters) and lua are scalar-only; an array input errors,
|
||||
// since their per-evaluation state cannot be split across array lanes.
|
||||
func (rg *runtimeGraph) evalSample(sourceVals map[string]dsp.Sample) (dsp.Sample, error) {
|
||||
vals := make(map[string]dsp.Sample, len(rg.order))
|
||||
vals, err := rg.evalSampleTrace(sourceVals)
|
||||
if err != nil {
|
||||
return dsp.Sample{}, err
|
||||
}
|
||||
return vals[rg.outputID], nil
|
||||
}
|
||||
|
||||
// evalSampleTrace runs a full forward pass like evalSample but returns the value
|
||||
// computed for *every* node (sources, ops and the output), keyed by node id. It
|
||||
// is used by the editor's live/debug trace to show each node's current value.
|
||||
// On an op error it returns the values computed so far together with the error,
|
||||
// so partial results can still be displayed.
|
||||
func (rg *runtimeGraph) evalSampleTrace(sourceVals map[string]dsp.Sample) (map[string]dsp.Sample, error) {
|
||||
vals := make(map[string]dsp.Sample, len(rg.order)+len(sourceVals))
|
||||
for id, v := range sourceVals {
|
||||
vals[id] = v
|
||||
}
|
||||
@@ -65,7 +78,7 @@ func (rg *runtimeGraph) evalSample(sourceVals map[string]dsp.Sample) (dsp.Sample
|
||||
}
|
||||
r, err := evalOp(n, in)
|
||||
if err != nil {
|
||||
return dsp.Sample{}, fmt.Errorf("node %s (%s): %w", n.id, n.op.Type(), err)
|
||||
return vals, fmt.Errorf("node %s (%s): %w", n.id, n.op.Type(), err)
|
||||
}
|
||||
vals[n.id] = r
|
||||
case "output":
|
||||
@@ -74,7 +87,7 @@ func (rg *runtimeGraph) evalSample(sourceVals map[string]dsp.Sample) (dsp.Sample
|
||||
}
|
||||
}
|
||||
}
|
||||
return vals[rg.outputID], nil
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
// evalOp runs a single op node over its Sample inputs, choosing the right
|
||||
|
||||
@@ -462,6 +462,65 @@ func outTypeOf(st *signalState) dsp.ValType {
|
||||
return st.rg.outType
|
||||
}
|
||||
|
||||
// TraceNode is a single node's computed value in a debug trace.
|
||||
type TraceNode struct {
|
||||
Value any `json:"value"`
|
||||
Type string `json:"type"` // "scalar" | "array"
|
||||
Approx bool `json:"approx,omitempty"`
|
||||
}
|
||||
|
||||
// TraceResult is the per-node outcome of a single-shot evaluation of an
|
||||
// (unsaved) synthetic graph, used by the editor's live/debug overlay.
|
||||
type TraceResult struct {
|
||||
Nodes map[string]TraceNode `json:"nodes"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// Trace compiles def and evaluates it once with fresh state, returning the value
|
||||
// computed for every node. Source node values are obtained via read(ds, name)
|
||||
// (typically a broker ReadNow snapshot); a source that cannot be read defaults to
|
||||
// scalar 0. Stateful ops are flagged Approx since their true running value
|
||||
// depends on accumulated state this single-shot pass does not have. A per-node
|
||||
// evaluation error is returned in Error with the partial values still populated.
|
||||
func (s *Synthetic) Trace(def SignalDef, read func(ds, name string) (any, error)) (TraceResult, error) {
|
||||
rg, err := compileGraph(def)
|
||||
if err != nil {
|
||||
return TraceResult{}, err
|
||||
}
|
||||
sourceVals := make(map[string]dsp.Sample, len(rg.sources))
|
||||
for _, src := range rg.sources {
|
||||
raw, rerr := read(src.ref.DS, src.ref.Name)
|
||||
if rerr != nil || raw == nil {
|
||||
sourceVals[src.id] = dsp.Scalar(0)
|
||||
continue
|
||||
}
|
||||
sourceVals[src.id] = toSample(raw)
|
||||
}
|
||||
vals, evalErr := rg.evalSampleTrace(sourceVals)
|
||||
|
||||
opType := make(map[string]string, len(rg.order))
|
||||
for _, n := range rg.order {
|
||||
if n.kind == "op" && n.op != nil {
|
||||
opType[n.id] = n.op.Type()
|
||||
}
|
||||
}
|
||||
res := TraceResult{Nodes: make(map[string]TraceNode, len(vals))}
|
||||
for id, sample := range vals {
|
||||
tn := TraceNode{Value: sample.AsAny(), Type: "scalar"}
|
||||
if sample.IsArray {
|
||||
tn.Type = "array"
|
||||
}
|
||||
if ot, ok := opType[id]; ok && dsp.IsStateful(ot) {
|
||||
tn.Approx = true
|
||||
}
|
||||
res.Nodes[id] = tn
|
||||
}
|
||||
if evalErr != nil {
|
||||
res.Error = evalErr.Error()
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// toSample coerces a datasource.Value.Data into a dsp.Sample: arrays become
|
||||
// array Samples (waveforms), everything else a scalar Sample.
|
||||
func toSample(v any) dsp.Sample {
|
||||
|
||||
Reference in New Issue
Block a user