Overhauled Data_Node to be functional

Implemented dynamically-configurable global timer
Revised Data_Node to be foundational for future nodes.
This commit is contained in:
2025-04-01 20:53:16 -06:00
parent feed26da98
commit d46c6ecc3c
3 changed files with 219 additions and 33 deletions

View File

@ -54,6 +54,11 @@ import ReactFlow, {
import "reactflow/dist/style.css";
import "./Borealis.css";
// Global Node Update Timer Variable
if (!window.BorealisUpdateRate) {
window.BorealisUpdateRate = 100; // Default Update Rate: 100ms
}
const nodeContext = require.context("./nodes", true, /\.jsx$/);
const nodeTypes = {};
const categorizedNodes = {};
@ -413,8 +418,42 @@ export default function App() {
</Box>
</Box>
<Box component="footer" sx={{ bgcolor: "#1e1e1e", color: "white", px: 2, py: 1, textAlign: "left" }}>
<b>Nodes</b>: <span id="nodeCount">0</span> | <b>Update Rate</b>: 500ms
<Box component="footer" sx={{ bgcolor: "#1e1e1e", color: "white", px: 2, py: 1, display: "flex", alignItems: "center", gap: 2 }}>
<b>Nodes</b>: <span id="nodeCount">0</span>
<Divider orientation="vertical" flexItem sx={{ borderColor: "#444" }} />
<b>Update Rate (ms):</b>
<input
id="updateRateInput"
type="number"
min="50"
step="50"
defaultValue={window.BorealisUpdateRate}
style={{
width: "80px",
background: "#121212",
color: "#fff",
border: "1px solid #444",
borderRadius: "3px",
padding: "3px",
fontSize: "0.8rem"
}}
/>
<Button
variant="outlined"
size="small"
onClick={() => {
const val = parseInt(document.getElementById("updateRateInput")?.value);
if (!isNaN(val) && val >= 50) {
window.BorealisUpdateRate = val;
console.log("Global update rate set to", val + "ms");
} else {
alert("Please enter a valid number (minimum 50)");
}
}}
sx={{ color: "#58a6ff", borderColor: "#58a6ff", fontSize: "0.75rem", textTransform: "none", px: 1.5 }}
>
Update Rate
</Button>
</Box>
</Box>

View File

@ -1,21 +1,190 @@
import React from "react";
import { Handle, Position } from "reactflow";
/**
* ============================================
* Borealis - Standard Live Data Node Template
* ============================================
*
* COMPONENT ROLE:
* This component defines a "data conduit" node that can accept input,
* process/override it with local logic, and forward the output on a timed basis.
*
* It serves as the core behavior model for other nodes that rely on live propagation.
* Clone and extend this file to create nodes with specialized logic.
*
* CORE CONCEPTS:
* - Uses a centralized shared memory (window.BorealisValueBus) for value sharing
* - Synchronizes with upstream nodes based on ReactFlow edges
* - Emits to downstream nodes by updating its own BorealisValueBus[id] value
* - Controlled by a global polling timer (window.BorealisUpdateRate)
*
* LIFECYCLE SUMMARY:
* - onMount: initializes logic loop and sync monitor
* - onUpdate: watches edges and global rate, reconfigures as needed
* - onUnmount: cleans up all timers
*
* DATA FLOW OVERVIEW:
* - INPUT: if a left-edge (target) is connected, disables manual editing
* - OUTPUT: propagates renderValue to downstream nodes via right-edge (source)
*
* STRUCTURE:
* - Node UI includes:
* * Label (from data.label)
* * Body description (from data.content)
* * Input textbox (disabled if input is connected)
*
* HOW TO EXTEND:
* - For transformations, insert logic into runNodeLogic()
* - To validate or restrict input types, modify handleManualInput()
* - For side-effects or external API calls, add hooks inside runNodeLogic()
*/
import React, { useEffect, useRef, useState } from "react";
import { Handle, Position, useReactFlow, useStore } from "reactflow";
// Global Shared Bus for Node Data Propagation
if (!window.BorealisValueBus) {
window.BorealisValueBus = {};
}
// Global Update Rate (ms) for All Data Nodes
if (!window.BorealisUpdateRate) {
window.BorealisUpdateRate = 100;
}
const DataNode = ({ id, data }) => {
const { setNodes } = useReactFlow();
const edges = useStore(state => state.edges);
const [renderValue, setRenderValue] = useState(data?.value || "");
const valueRef = useRef(renderValue);
// Manual input handler (disabled if connected to input)
const handleManualInput = (e) => {
const newValue = e.target.value;
// TODO: Add input validation/sanitization here if needed
valueRef.current = newValue;
setRenderValue(newValue);
window.BorealisValueBus[id] = newValue;
setNodes(nds =>
nds.map(n =>
n.id === id
? { ...n, data: { ...n.data, value: newValue } }
: n
)
);
};
useEffect(() => {
let currentRate = window.BorealisUpdateRate || 100;
let intervalId = null;
const runNodeLogic = () => {
const inputEdge = edges.find(e => e?.target === id);
const hasInput = Boolean(inputEdge);
if (hasInput && inputEdge.source) {
const upstreamValue = window.BorealisValueBus[inputEdge.source] ?? "";
// TODO: Insert custom transform logic here (e.g., parseInt, apply formula)
if (upstreamValue !== valueRef.current) {
valueRef.current = upstreamValue;
setRenderValue(upstreamValue);
window.BorealisValueBus[id] = upstreamValue;
setNodes(nds =>
nds.map(n =>
n.id === id
? { ...n, data: { ...n.data, value: upstreamValue } }
: n
)
);
}
} else {
// OUTPUT BROADCAST: emits to downstream via shared memory
window.BorealisValueBus[id] = valueRef.current;
}
};
const startInterval = () => {
intervalId = setInterval(runNodeLogic, currentRate);
};
startInterval();
// Monitor for global update rate changes
const monitor = setInterval(() => {
const newRate = window.BorealisUpdateRate || 100;
if (newRate !== currentRate) {
currentRate = newRate;
clearInterval(intervalId);
startInterval();
}
}, 250);
return () => {
clearInterval(intervalId);
clearInterval(monitor);
};
}, [id, setNodes, edges]);
const inputEdge = edges.find(e => e?.target === id);
const hasInput = Boolean(inputEdge);
const upstreamId = inputEdge?.source || "";
const upstreamValue = window.BorealisValueBus[upstreamId] || "";
const dataNode = ({ data }) => {
return (
<div className="borealis-node">
<Handle type="target" position={Position.Left} className="borealis-handle" />
<div className="borealis-node-header">
{data?.label || "Data Node"}
</div>
<div className="borealis-node-content">
{/* Description visible in node body */}
<div style={{ marginBottom: "8px", fontSize: "9px", color: "#ccc" }}>
{data?.content || "Foundational node for live value propagation."}
</div>
<label style={{ fontSize: "9px", display: "block", marginBottom: "4px" }}>
Value:
</label>
<input
type="text"
value={renderValue}
onChange={handleManualInput}
disabled={hasInput}
style={{
fontSize: "9px",
padding: "4px",
background: hasInput ? "#2a2a2a" : "#1e1e1e",
color: "#ccc",
border: "1px solid #444",
borderRadius: "2px",
width: "100%"
}}
/>
</div>
<Handle type="source" position={Position.Right} className="borealis-handle" />
<div className="borealis-node-header">{data.label || "Data Node"}</div>
<div className="borealis-node-content">{data.content || "Placeholder Data Content"}</div>
</div>
);
};
export default {
type: "dataNode",
type: "DataNode", // REQUIRED: unique identifier for the node type
label: "Data Node",
description: "This node acts as a baseline foundational example for all nodes to follow.",
defaultContent: "Placeholder Node",
component: dataNode
description: `
Foundational Data Node
- Accepts input from another node
- If no input is connected, allows user-defined value
- Pushes value to downstream nodes every X ms
- Uses BorealisValueBus to communicate with other nodes
`.trim(),
content: "Store Strings, Ints, and Floats",
component: DataNode
};