diff --git a/.gitignore b/.gitignore index 9b79cfb..4a94c7a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .vscode/ -*.exe \ No newline at end of file +*.exe +utils/test.go \ No newline at end of file diff --git a/blackprint/blackprint.go b/blackprint/blackprint.go index 90a1535..7bb4ddb 100644 --- a/blackprint/blackprint.go +++ b/blackprint/blackprint.go @@ -7,15 +7,19 @@ import ( ) // The constructor must return pointer (ex: &Node{}) -func RegisterNode(namespace string, constructor func(*engine.Instance) any) { - engine.QNodeList[namespace] = constructor +func RegisterNode(namespace string, meta *engine.NodeRegister) { + engine.QNodeList[namespace] = meta } // The constructor must return pointer (ex: &any) -func RegisterInterface(namespace string, constructor func(any) any) { +func RegisterInterface(namespace string, meta *engine.InterfaceRegister) { if strings.HasPrefix(namespace, "BPIC/") == false { panic(namespace + ": The first parameter of 'RegisterInterface' must be started with BPIC to avoid name conflict. Please name the interface similar with 'templatePrefix' for your module that you have set on 'blackprint.config.js'.") } - engine.QInterfaceList[namespace] = constructor + engine.QInterfaceList[namespace] = meta } + +var Event = engine.Event +var Environment = engine.QEnvironment +var Port = engine.QPorts diff --git a/engine/bpVar.go b/engine/bpVar.go new file mode 100644 index 0000000..93149ed --- /dev/null +++ b/engine/bpVar.go @@ -0,0 +1,40 @@ +package engine + +import ( + "reflect" +) + +type bpVarValue struct { + *CustomEvent + val any +} + +func (b *bpVarValue) Get() any { + return b.val +} + +func (b *bpVarValue) Set(val any) { + b.val = val + b.Emit("value", nil) +} + +// used for instance.CreateVariable +type BPVariable struct { + *CustomEvent + Id string + Title string + Type reflect.Kind + Used []*Interface + Value bpVarValue + Listener []*Interface + FuncInstance *bpFunction // for shared function variables +} + +func (b *BPVariable) Destroy() { + for _, iface := range b.Used { + ins := iface.Node.Instance + ins.DeleteNode(iface) + } + + b.Used = b.Used[:0] +} diff --git a/engine/cable.go b/engine/cable.go index 759b51c..6161a1f 100644 --- a/engine/cable.go +++ b/engine/cable.go @@ -2,12 +2,22 @@ package engine import ( "reflect" + + "github.com/blackprint/engine-go/utils" ) type Cable struct { - Type reflect.Kind - Owner *Port - Target *Port + Type reflect.Kind + Owner *Port + Target *Port + Input *Port + Output *Port + Source int + Disabled int + IsRoute bool + Connected bool + _evDisconnected bool + _ghost bool } type CableEvent struct { @@ -16,42 +26,136 @@ type CableEvent struct { Target *Port } -func NewCable(owner *Port, target *Port) Cable { - return Cable{ +type PortValueEvent = CableEvent + +func newCable(owner *Port, target *Port) *Cable { + var input *Port + var output *Port + + if owner.Source == PortInput { + input = owner + output = target + } else { + output = owner + input = target + } + + return &Cable{ Type: owner.Type, + Source: owner.Source, Owner: owner, Target: target, + Input: input, + Output: output, } } -func (c *Cable) QConnected() { - c.Owner.QTrigger("cable.connect", CableEvent{ +func (c *Cable) _connected() { + c.Connected = true + + ownerEv := &CableEvent{ Cable: c, Port: c.Owner, Target: c.Target, - }) + } + c.Owner.Emit("cable.connect", ownerEv) + c.Owner.Iface.Emit("cable.connect", ownerEv) - c.Target.QTrigger("cable.connect", CableEvent{ + targetEv := &CableEvent{ Cable: c, Port: c.Target, Target: c.Owner, - }) + } + c.Target.Emit("cable.connect", targetEv) + c.Target.Iface.Emit("cable.connect", targetEv) - var inp, out *Port - if c.Owner.Source == PortInput { - inp = c.Owner - out = c.Target - } else { - inp = c.Target - out = c.Owner + if c.Output.Value == nil { + return } - if out.Value != nil { - inp.QTrigger("value", out) + inputEv := &PortValueEvent{ + Port: c.Output, } + c.Input.Emit("value", inputEv) + c.Input.Iface.Emit("value", inputEv) + + c.Input.Iface.Node.Update(c) } // For debugging func (c *Cable) String() string { - return "\nCable: " + c.Owner.Iface.Title + "." + c.Owner.Name + " <=> " + c.Target.Name + "." + c.Target.Iface.Title + return "\nCable: " + c.Output.Iface.Title + "." + c.Output.Name + " <=> " + c.Input.Name + "." + c.Input.Iface.Title +} + +func (c *Cable) GetValue() any { + return c.Output.Value +} + +func (c *Cable) Disconnect(which_ ...*Port) { // which = port + if c.IsRoute { // ToDo: simplify, use 'which' instead of check all + if c.Output.Cables != nil { + c.Output.Cables = c.Output.Cables[:0] + } else if c.Output.RoutePort.Out == c { + c.Output.RoutePort.Out = nil + } else if c.Input.RoutePort.Out == c { + c.Input.RoutePort.Out = nil + } + + c.Output.RoutePort.In = utils.RemoveItem(c.Output.RoutePort.In, c) + c.Input.RoutePort.In = utils.RemoveItem(c.Input.RoutePort.In, c) + + c.Connected = false + return + } + + hasWhich := len(which_) == 0 + which := which_[0] + alreadyEmitToInstance := false + + if c.Input != nil { + c.Input._cache = nil + } + + if c.Owner != nil && (!hasWhich || which == c.Owner) { + c.Owner.Cables = utils.RemoveItem(c.Owner.Cables, c) + + if c.Connected { + temp := &CableEvent{ + Cable: c, + Port: c.Owner, + Target: c.Target, + } + + c.Owner.Emit("disconnect", temp) + c.Owner.Iface.Emit("cable.disconnect", temp) + ins := c.Owner.Iface.Node.Instance + ins.Emit("cable.disconnect", temp) + + alreadyEmitToInstance = true + } else { + c.Owner.Iface.Emit("cable.cancel", &CableEvent{ + Cable: c, + Port: c.Owner, + Target: nil, + }) + } + } + + if c.Target != nil && c.Connected && (!hasWhich || which == c.Target) { + c.Target.Cables = utils.RemoveItem(c.Target.Cables, c) + + temp := &CableEvent{ + Cable: c, + Port: c.Target, + Target: c.Owner, + } + + c.Target.Emit("disconnect", temp) + c.Target.Iface.Emit("cable.disconnect", temp) + + if alreadyEmitToInstance == false { + ins := c.Target.Iface.Node.Instance + ins.Emit("cable.disconnect", temp) + } + } } diff --git a/engine/customevent.go b/engine/customevent.go index e5d4b73..3ddcbeb 100644 --- a/engine/customevent.go +++ b/engine/customevent.go @@ -2,6 +2,8 @@ package engine import ( "strings" + + "github.com/blackprint/engine-go/utils" ) type eventObj struct { @@ -9,16 +11,16 @@ type eventObj struct { once bool } -type customEvent struct { +type CustomEvent struct { events map[string][]*eventObj } -func (e *customEvent) listen(evName string, callback any, once bool) { +func (e *CustomEvent) listen(evName string, callback any, once bool) { if e.events == nil { e.events = map[string][]*eventObj{} } - evs := strings.Split(evName, ",") + evs := strings.Split(evName, " ") for _, name := range evs { list := e.events[name] @@ -33,7 +35,7 @@ func (e *customEvent) listen(evName string, callback any, once bool) { } if exist { - break + continue } e.events[name] = append(list, &eventObj{ @@ -43,43 +45,47 @@ func (e *customEvent) listen(evName string, callback any, once bool) { } } -func (e *customEvent) On(evName string, callback any) { +func (e *CustomEvent) On(evName string, callback any) { e.listen(evName, callback, false) } -func (e *customEvent) Once(evName string, callback any) { +func (e *CustomEvent) Once(evName string, callback any) { e.listen(evName, callback, true) } -func (e *customEvent) Off(evName string, callback any) { +func (e *CustomEvent) Off(evName string, callback any) { if e.events == nil { return } - evs := strings.Split(evName, ",") + evs := strings.Split(evName, " ") for _, name := range evs { if callback == nil { e.events[name] = []*eventObj{} - break + continue } list := e.events[name] + if list == nil { + continue + } + for i, cb := range list { if cb.callback == callback { - e.events[name] = append(list[:i], list[i+1:]...) - break + e.events[name] = utils.RemoveItemAtIndex(list, i) + continue } } } } -func (e *customEvent) QTrigger(evName string, data any) { +func (e *CustomEvent) Emit(evName string, data any) { list := e.events[evName] for i, cb := range list { cb.callback.(func(any))(data) if cb.once { - e.events[evName] = append(list[:i], list[i+1:]...) + e.events[evName] = utils.RemoveItemAtIndex(list, i) } } } diff --git a/engine/engine.go b/engine/engine.go index 470f07e..aebaf55 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -3,139 +3,247 @@ package engine import ( "encoding/json" "fmt" - "reflect" + "regexp" + "strings" + "github.com/blackprint/engine-go/engine/nodes" + "github.com/blackprint/engine-go/types" "github.com/blackprint/engine-go/utils" ) -type NodePort map[string]any +var Event = &CustomEvent{} +type PortTemplate map[string]any // any = reflect.Kind | portFeature type Instance struct { - IFace map[string]any // Storing with node id if exist - IFaceList map[int]any // Storing with node index - settings map[string]bool + *CustomEvent + Iface map[string]*Interface // Storing with node id if exist + IfaceList []*Interface // Storing with node index + settings map[string]bool + DisablePorts bool + PanicOnError bool + + Variables map[string]*BPVariable + Functions map[string]*bpFunction + Ref map[string]*referencesShortcut + + // For internal library use only + sharedVariables map[string]*BPVariable + _funcMain *Interface + _funcInstance *bpFunction + _mainInstance *Instance + _remote any } func New() *Instance { return &Instance{ - IFace: map[string]any{}, - IFaceList: map[int]any{}, - settings: map[string]bool{}, + Iface: map[string]*Interface{}, + IfaceList: []*Interface{}, + settings: map[string]bool{}, + PanicOnError: true, } } -type Data struct { +type dataX struct { Value string `json:"value"` } -type Namespace string -type NodeData struct { - Data Data `json:"data,omitempty"` +type nodeData struct { + Data dataX `json:"data,omitempty"` } -type NodeOutput struct { +type nodeOutput struct { Output []Node `json:"output"` } -type NodeX struct { +type nodeX struct { Name string `json:"name"` I *int64 `json:"i,omitempty"` ID *string `json:"id,omitempty"` - NodeData - NodeOutput + nodeData + nodeOutput } -type DataStructure map[Namespace][]NodeX +type namespace string +type dataStructure map[namespace][]nodeX // -type SingleInstanceJSON map[string]nodeList -type metaValue map[string]string +type singleInstanceJSON map[string]any // any = nodeList | metadataValue +type metadataValue map[string]any type nodeList []nodeConfig type nodeConfig struct { - I int `json:"i"` - Id string `json:"id"` - Data any `json:"data"` - Output map[string][]nodePortTarget `json:"output"` + I int `json:"i"` + Id string `json:"id"` + Data any `json:"data"` + Output map[string][]nodePortTarget `json:"output"` + InputDefault map[string]any `json:"input_d"` + OutputSwitch map[string]uint `json:"output_sw"` + Route map[string]uint `json:"route"` } type nodePortTarget struct { I int `json:"i"` Name string `json:"name"` } -type GetterSetter interface { +type getterSetter interface { Set(val any) Get() any } -func (instance *Instance) ImportJSON(str []byte) (err error) { - var data SingleInstanceJSON +type GetterSetter struct { + Value any + Iface *Interface +} + +func (b *GetterSetter) Get() any { + return b.Value +} + +func (b *GetterSetter) Set(Value any) { + b.Value = Value +} + +type ImportOptions struct { + AppendMode bool +} + +func (instance *Instance) ImportJSON(str []byte, options ...ImportOptions) (inserted []*Interface, err error) { + var data singleInstanceJSON err = json.Unmarshal(str, &data) if err != nil { return } - ifaceList := instance.IFaceList - var nodes []any + return instance.importParsed(data, options...) +} + +func (instance *Instance) importParsed(data singleInstanceJSON, options ...ImportOptions) (inserted []*Interface, err error) { + hasOption := len(options) != 0 + options_ := options[0] + + if hasOption && options_.AppendMode == false { + instance.ClearNodes() + } + + // Do we need this? + // instance.Emit("json.importing", {appendMode: options.appendMode, raw: json}) + + ifaceList := instance.IfaceList + var metadata metadataValue + + appendLength := 0 + if options_.AppendMode { + appendLength = len(ifaceList) + } + + var exist bool + if metadata, exist = data["_"].(metadataValue); exist { + if list, exist := metadata["env"]; exist { + QEnvironment.Import(list.(map[string]string)) + } + + if list, exist := metadata["functions"].(map[string]any); exist { + for key, options := range list { + instance.CreateFunction(key, options) + } + } + + if list, exist := metadata["variables"].(map[string]any); exist { + for key, options := range list { + instance.CreateVariable(key, options) + } + } + + delete(data, "_") + } // Prepare all ifaces based on the namespace // before we create cables for them for namespace, ifaces := range data { - if namespace == "_" { - // meta := ifaces.(metaValue) - continue - } + // Every ifaces that using this namespace name + for _, iface := range ifaces.(nodeList) { + iface.I += appendLength - list := ifaces //.(nodeList) + var temp *Interface + temp, inserted = instance.CreateNode(namespace, iface, inserted) - // Every ifaces that using this namespace name - for _, iface := range list { - ifaceList[iface.I], nodes = instance.CreateNode(namespace, iface, nodes) + ifaceList[iface.I] = temp + temp._bpFnInit() } } // Create cable only from output and property // > Important to be separated from above, so the cable can reference to loaded ifaces for _, ifaces := range data { - list := ifaces //.(nodeList) + list := ifaces.(nodeList) + + for _, ifaceJSON := range list { + iface := ifaceList[ifaceJSON.I] - for _, iface := range list { - current := ifaceList[iface.I] + if val := ifaceJSON.Route; val != nil { + iface.Node.Routes.RouteTo(ifaceList[val["i"]]) + } // If have output connection - out := iface.Output + out := ifaceJSON.Output if out != nil { - Output := *utils.GetPropertyRef(current, "Output").(*map[string]*Port) + Output := iface.Output // Every output port that have connection for portName, ports := range out { linkPortA := Output[portName] if linkPortA == nil { - panic(fmt.Sprintf("Node port not found for iface (index: %d), with name: %s", iface.I, portName)) + if iface._enum == nodes.BPFnInput { + target := instance._getTargetPortType(iface.Node.Instance, "input", ports) + linkPortA = iface.Embed.(*qBpFnInOut).AddPort(target, portName) + + if linkPortA == nil { + panic(fmt.Sprintf("Can't create output port (%s) for function (%s)", portName, iface._funcMain.Node._funcInstance.Id)) + } + } else if iface._enum == nodes.BPVarGet { + target := instance._getTargetPortType(instance, "input", ports) + iface.Embed.(*iVarGet).UseType(target) + linkPortA = iface.Output[portName] + } else { + panic(fmt.Sprintf("Node port not found for iface (index: %d), with name: %s", ifaceJSON.I, portName)) + } } // Current output's available targets for _, target := range ports { + target.I += appendLength targetNode := ifaceList[target.I] - Input := *utils.GetPropertyRef(targetNode, "Input").(*map[string]*Port) + // output can only meet input port + Input := targetNode.Input linkPortB := Input[target.Name] if linkPortB == nil { - targetTitle := utils.GetProperty(targetNode, "Title").(string) - panic(fmt.Sprintf("Node port not found for %s with name: %s", targetTitle, target.Name)) + targetTitle := targetNode.Title + + if targetNode._enum == nodes.BPFnOutput { + linkPortB = targetNode.Embed.(*qBpFnInOut).AddPort(linkPortA, portName) + + if linkPortB == nil { + panic(fmt.Sprintf("Can't create output port (%s) for function (%s)", portName, targetNode._funcMain.Node._funcInstance.Id)) + } + } else if targetNode._enum == nodes.BPVarGet { + target := instance._getTargetPortType(instance, "input", ports) + targetNode.Embed.(*iVarGet).UseType(target) + linkPortB = targetNode.Input[target.Name] + } else if linkPortA.Type == types.Route { + linkPortB = targetNode.Node.Routes.Port + } else { + panic(fmt.Sprintf("Node port not found for %s with name: %s", targetTitle, target.Name)) + } } // For Debugging -> - // Title := utils.GetProperty(current, "Title").(string) - // targetTitle := utils.GetProperty(targetNode, "Title").(string) + // Title := iface.Title + // targetTitle := targetNode.Title // fmt.Printf("%s.%s => %s.%s\n", Title, linkPortA.Name, targetTitle, linkPortB.Name) // <- For Debugging - cable := NewCable(linkPortA, linkPortB) - linkPortA.Cables = append(linkPortA.Cables, &cable) - linkPortB.Cables = append(linkPortB.Cables, &cable) - - cable.QConnected() + linkPortA.ConnectPort(linkPortB) // fmt.Println(cable.String()) } } @@ -144,10 +252,79 @@ func (instance *Instance) ImportJSON(str []byte) (err error) { } // Call nodes init after creation processes was finished - for _, val := range nodes { - utils.CallFunction(val, "Init", utils.EmptyArgs) + for _, val := range inserted { + val.Init() } + return + +} + +func (instance *Instance) _getTargetPortType(ins *Instance, which string, targetNodes []nodePortTarget) *Port { + target := targetNodes[0] // ToDo: check all target in case if it's supporting Union type + targetIface := ins.IfaceList[target.I] + + if which == "input" { + return targetIface.Input[target.Name] + } else { + return targetIface.Output[target.Name] + } +} + +type NodeDeleteEvent struct { + Iface any +} + +func (instance *Instance) DeleteNode(iface *Interface) { + i := utils.IndexOf(instance.IfaceList, iface) + if i == -1 { + panic("Node to be deleted was not found") + } + + instance.IfaceList = utils.RemoveItemAtIndex(instance.IfaceList, i) + + eventData := &NodeDeleteEvent{ + Iface: iface, + } + instance.Emit("node.delete", eventData) + + iface.Node.Destroy() + iface.Destroy() + + for _, port := range iface.Output { + port.DisconnectAll() + } + + routes := iface.Node.Routes + for _, cable := range routes.In { + cable.Disconnect() + } + + if routes.Out != nil { + routes.Out.Disconnect() + } + + // Delete reference + delete(instance.Iface, iface.Id) + delete(instance.Ref, iface.Id) + + parent := iface.Node.Instance._funcInstance + if parent != nil { + delete(parent.RootInstance.Ref, iface.Id) + } + + instance.Emit("node.deleted", eventData) +} + +func (instance *Instance) ClearNodes() { + for _, iface := range instance.IfaceList { + iface.Node.Destroy() + iface.Destroy() + } + + instance.IfaceList = instance.IfaceList[:0] + utils.ClearMap(instance.Iface) + utils.ClearMap(instance.Ref) } func (instance *Instance) Settings(id string, val ...bool) bool { @@ -160,83 +337,260 @@ func (instance *Instance) Settings(id string, val ...bool) bool { return temp } -func (instance *Instance) GetNode(id any) any { - for _, val := range instance.IFaceList { - temp := reflect.ValueOf(val).Elem() - if temp.FieldByName("Id").Interface().(string) == id || temp.FieldByName("I").Interface().(int) == id { - return utils.GetProperty(val, "Node") +// Deprecated, use instance.Iface or instance.IfaceList instead +func (instance *Instance) GetNode(id any) *Interface { + for _, val := range instance.IfaceList { + if val.Id == id.(string) || val.I == id.(int) { + return val } } return nil } -func (instance *Instance) GetNodes(namespace string) []any { - var got []any // any = extends 'engine.Node' +func (instance *Instance) GetNodes(namespace string) []*Interface { + var got []*Interface - for _, val := range instance.IFaceList { - if utils.GetProperty(val, "Namespace").(string) == namespace { - got = append(got, utils.GetProperty(val, "Node")) + for _, val := range instance.IfaceList { + if val.Namespace == namespace { + got = append(got, val) } } return got } -func (instance *Instance) CreateNode(namespace string, options nodeConfig, nodes []any) (any, []any) { +// ToDo: sync with JS, when creating function node this still broken +func (instance *Instance) CreateNode(namespace string, options nodeConfig, nodes []*Interface) (*Interface, []*Interface) { func_ := QNodeList[namespace] + var node *Node + var isFuncNode bool + if func_ == nil { - panic("Node nodes for " + namespace + " was not found, maybe .registerNode() haven't being called?") + if strings.HasPrefix(namespace, "BPI/F/") { + temp := instance.Functions[namespace] + if temp != nil { + node = temp.Node(instance) + } + + isFuncNode = true + } + + if node == nil { + panic("Node nodes for " + namespace + " was not found, maybe .registerNode() haven't being called?") + } + } else { + node = &Node{Instance: instance} + func_.Constructor(node) + } + + // Disable data flow on any node ports + if instance.DisablePorts { + node.DisablePorts = true } - // *node: extends engine.Node - node := func_(instance) // func_ from registerNode(namespace, func_) if utils.IsPointer(node) == false { panic(namespace + ": .registerNode() must return pointer") } // *iface: extends engine.Interface - iface := utils.GetProperty(node, "IFace") - if iface == nil || utils.GetProperty(iface, "QInitialized").(bool) == false { - panic(namespace + ": Node interface was not found, do you forget to call node->setInterface() ?") + iface := node.Iface + if iface == nil || iface._initialized == false { + panic(namespace + ": Node interface was not found, do you forget to call node.SetInterface() ?") } - utils.SetProperty(iface, "Node", node) + iface.Node = node + iface.Namespace = namespace - // Assign the saved options if exist - // Must be called here to avoid port trigger - if options.Data != nil { - data := utils.GetPropertyRef(iface, "Data").(*InterfaceData) - if data != nil { - deepMerge(data, options.Data.(map[string]any)) + // Create the linker between the nodes and the iface + if isFuncNode == false { + iface._prepare(func_) + } + + if options.Id != "" { + iface.Id = options.Id + instance.Iface[options.Id] = iface + instance.Ref[options.Id] = iface.Ref + + parent := iface.Node._funcInstance + if parent != nil { + parent.RootInstance.Ref[options.Id] = iface.Ref } } - utils.SetProperty(iface, "Namespace", namespace) + iface.I = options.I + instance.IfaceList[options.I] = iface - // Create the linker between the nodes and the iface - utils.CallFunction(iface, "QPrepare", utils.EmptyArgs) + if options.InputDefault != nil { + iface._importInputs(options.InputDefault) + } - if options.Id != "" { - utils.SetProperty(iface, "Id", options.Id) - instance.IFace[options.Id] = iface + savedData := options.Data.(map[string]any) + + if options.OutputSwitch != nil { + for key, val := range options.OutputSwitch { + if (val | 1) == 1 { + portStructOf_split(iface.Output[key]) + } + + if (val | 2) == 2 { + iface.Output[key].AllowResync = true + } + } } - utils.SetProperty(iface, "I", options.I) - instance.IFaceList[options.I] = iface + iface.Importing = false - utils.SetProperty(iface, "Importing", false) - utils.CallFunction(node, "Imported", utils.EmptyArgs) + iface.Imported(savedData) + node.Imported(savedData) + iface.Init() if nodes != nil { - nodes = append(nodes, node) + nodes = append(nodes, iface) + } else { + // Init now if not A batch creation + node.Init() } - utils.CallFunction(node, "Init", utils.EmptyArgs) - utils.CallFunction(iface, "Init", utils.EmptyArgs) - return iface, nodes } +var createBPVariableRegx = regexp.MustCompile(`[` + "`" + `~!@#$%^&*()\-_+={}\[\]:"|;\'\\\\,.\/<>?]+`) + +type varOptions struct { + Id string `json:"id"` + Title string `json:"title"` +} + +func (instance *Instance) CreateVariable(id string, options any) *BPVariable { + id = createBPVariableRegx.ReplaceAllString(id, "_") + + if old, exist := instance.Variables[id]; exist { + old.Destroy() + delete(instance.Variables, id) + } + + // options_ = options.(varOptions) + + temp := &BPVariable{ + Id: id, + Title: id, + Type: 0, // Type not set + } + instance.Variables[id] = temp + instance.Emit("variable.new", temp) + + return temp +} + +type funcOptions struct { + Id string `json:"id"` + Title string `json:"title"` + Vars []string `json:"vars"` + privateVars []string `json:"privateVars"` + Structure singleInstanceJSON `json:"structure"` +} + +func (instance *Instance) CreateFunction(id string, options any) *bpFunction { + id = createBPVariableRegx.ReplaceAllString(id, "_") + + if old, exist := instance.Functions[id]; exist { + old.Destroy() + delete(instance.Functions, id) + } + + options_ := options.(funcOptions) + + // This will be updated if the function sketch was modified + structure := options_.Structure + if structure == nil { + structure = singleInstanceJSON{ + "BP/Fn/Input": nodeList{nodeConfig{I: 0}}, + "BP/Fn/Output": nodeList{nodeConfig{I: 1}}, + } + } + + title := id + temp := &bpFunction{ + Id: id, + Title: title, + Type: 0, // Type not set + Structure: structure, + RootInstance: instance, + Input: PortTemplate{}, + Output: PortTemplate{}, + } + + meta := &NodeRegister{ + Input: temp.Input, + Output: temp.Output, + } + + uniqId := 0 + temp.Node = func(ins *Instance) *Node { + ins._funcInstance = temp + + node := &Node{ + Instance: ins, + _funcInstance: temp, + } + + node.Embed = &bpFunctionNode{} + + iface := node.SetInterface("BPIC/BP/Fn/Main") + iface.Embed.(*bpFunctionNode).Type = "function" + iface._enum = nodes.BPFnMain + iface.Namespace = id + iface.Title = title + + uniqId += 1 + iface.uniqId = uniqId + + iface._prepare(meta) + return node + } + + instance.Functions[id] = temp + + for _, val := range options_.Vars { + temp.CreateVariable(val, FnVarOptions{ + Scope: VarScopeShared, + }) + } + + for _, val := range options_.privateVars { + temp.AddPrivateVars(val) + } + + instance.Emit("function.new", temp) + return temp +} + +type NodeLogEvent struct { + Instance *Instance + Iface *Interface + IfaceTitle string + Message string +} + +func (instance *Instance) _log(iface *Interface, message string) { + evData := NodeLogEvent{ + Instance: instance, + Iface: iface, + IfaceTitle: iface.Title, + Message: message, + } + + if instance._mainInstance != nil { + instance._mainInstance.Emit("log", evData) + } else { + instance.Emit("log", evData) + } +} + +func (instance *Instance) Destroy() { + instance.ClearNodes() +} + // Currently only one level func deepMerge(real_ *InterfaceData, merge map[string]any) { real := *real_ diff --git a/engine/enums.go b/engine/enums.go new file mode 100644 index 0000000..c4df247 --- /dev/null +++ b/engine/enums.go @@ -0,0 +1,8 @@ +package engine + +/** For internal library use only */ +const ( + VarScopePublic = iota + 1 + VarScopePrivate + VarScopeShared +) diff --git a/engine/environment.go b/engine/environment.go new file mode 100644 index 0000000..aee2642 --- /dev/null +++ b/engine/environment.go @@ -0,0 +1,52 @@ +package engine + +import "regexp" + +type environment struct { + _noEvent bool + Map map[string]string +} + +var QEnvironment = &environment{ + Map: map[string]string{}, +} + +// arr = ["KEY": "value"] +func (e *environment) Import(arr map[string]string) { + e._noEvent = true + for key, val := range arr { + e.Set(key, val) + } + e._noEvent = false + Event.Emit("environment.imported", nil) +} + +var envSetRegx = regexp.MustCompile(`[^A-Z_][^A-Z0-9_]`) + +type EnvironmentEvent struct { + Key string + Value string +} + +func (e *environment) Set(key string, val string) { + if len(envSetRegx.FindStringIndex(key)) > 0 { + panic("Environment must be uppercase and not contain any symbol except underscore, and not started by a number. But got: " + key) + } + + e.Map[key] = val + + if !e._noEvent { + Event.Emit("environment.added", &EnvironmentEvent{ + Key: key, + Value: val, + }) + } +} + +func (e *environment) Delete(key string) { + delete(e.Map, key) + Event.Emit("environment.deleted", &EnvironmentEvent{ + Key: key, + Value: "", + }) +} diff --git a/engine/interface.go b/engine/interface.go index 2ac32e9..d12ea4b 100644 --- a/engine/interface.go +++ b/engine/interface.go @@ -9,126 +9,217 @@ import ( var portList = [3]string{"Input", "Output", "Property"} -type InterfaceData map[string]GetterSetter +type embedInterface interface { + Init() + Request(*Cable) + Update(*Cable) + Imported(map[string]any) + Destroy() + SyncIn(id string, data ...any) +} + +type EmbedInterface struct { + embedInterface + Node *Node + Iface *Interface + Ref *referencesShortcut +} + +// To be overriden by module developer +func (iface *EmbedInterface) Init() {} +func (iface *EmbedInterface) Destroy() {} +func (iface *EmbedInterface) Imported(data map[string]any) {} + +type InterfaceData map[string]getterSetter type Interface struct { - *customEvent - QInitialized bool // for internal library only + *CustomEvent Id string + uniqId int // For bpFunction only I int // index Title string Namespace string - Output map[string]*Port - Input map[string]*Port - Property map[string]*Port - Data InterfaceData - Node any // any = extends *engine.Node - - QRequesting bool // private (to be used for internal library only) - Importing bool + // Property map[string]*Port + Output map[string]*Port + Input map[string]*Port + Data InterfaceData + Node *Node + Embed embedInterface + + Ref *referencesShortcut + IsGhost bool + + Importing bool + + // for internal library use only + _initialized bool + _requesting bool + _funcMain *Interface + _dynamicPort bool + _enum int + _bpVarRef *BPVariable + _proxyInput *Node + _proxyOutput *Node + _parentFunc *Interface + _bpInstance *Instance + _bpDestroy bool } // To be overriden -func (iface *Interface) Init() {} +func (i *Interface) Init() { i.Embed.Init() } +func (i *Interface) Destroy() { i.Embed.Destroy() } +func (i *Interface) Imported(data map[string]any) { i.Embed.Imported(data) } + +// Internal blackprint function node initialization +func (iface *Interface) _bpFnInit() {} var reflectKind = reflect.TypeOf(reflect.Int) // Private (to be called for internal library only) -func (iface *Interface) QPrepare() { - iface.customEvent = &customEvent{} +func (iface *Interface) _prepare(meta *NodeRegister) { + iface.CustomEvent = &CustomEvent{} + ref := &referencesShortcut{} node := iface.Node + node.Ref = ref + iface.Ref = ref + + node.Routes = &routePort{Iface: iface} for i := 0; i < 3; i++ { which := portList[i] - port := *utils.GetPropertyRef(node, "T"+which).(*map[string]any) // get value by property name + port := utils.GetProperty(meta, which).(PortTemplate) // get value by property name if port == nil { continue } ifacePort := map[string]*Port{} - utils.SetProperty(iface, which, ifacePort) - var inputUpgradePort map[string]*PortInputGetterSetter - var outputUpgradePort map[string]*PortOutputGetterSetter + var inputUpgradePort map[string]*portInputGetterSetter + var outputUpgradePort map[string]*portOutputGetterSetter if which == "Input" { - inputUpgradePort = map[string]*PortInputGetterSetter{} - utils.SetProperty(node, which, inputUpgradePort) + inputUpgradePort = map[string]*portInputGetterSetter{} + ref.Input = inputUpgradePort + ref.IInput = ifacePort + + iface.Input = ifacePort + node.Input = inputUpgradePort } else { - outputUpgradePort = map[string]*PortOutputGetterSetter{} - utils.SetProperty(node, which, outputUpgradePort) + outputUpgradePort = map[string]*portOutputGetterSetter{} + ref.Output = outputUpgradePort + ref.IOutput = ifacePort + + iface.Output = ifacePort + node.Output = outputUpgradePort } // name: string for name, config_ := range port { - var config *PortFeature - var type_ reflect.Kind - var feature int - - var def any - if reflect.TypeOf(config_) == reflectKind { - type_ = config_.(reflect.Kind) - - if type_ == types.Int { - def = 0 - } else if type_ == types.Bool { - def = false - } else if type_ == types.String { - def = "" - } else if type_ == types.Array { - def = [0]any{} // ToDo: is this actually working? - } else if type_ == types.Any { // Any - // pass - } else if type_ == types.Function { - // pass - } else { - panic(iface.Namespace + ": '" + name + "' Port type(" + type_.String() + ") for initialization was not recognized") - } - } else { - config = config_.(*PortFeature) - type_ = config.Type - feature = config.Id - - if feature == PortTypeTrigger { - def = config.Func - type_ = types.Function - } else if feature == PortTypeArrayOf { - // pass - } else { - // panic(iface.Namespace + ": '" + name + "' Port feature(" + strconv.Itoa(feature) + ") for initialization was not recognized") - } - } + linkedPort := iface._createPort(which, name, config_) + ifacePort[name] = linkedPort - var source int + // CreateLinker() if which == "Input" { - source = PortInput - } else if which == "Output" { - source = PortOutput + inputUpgradePort[name] = &portInputGetterSetter{port: linkedPort} + } else { + outputUpgradePort[name] = &portOutputGetterSetter{port: linkedPort} } - // else if which == "Property" { - // source = PortProperty - // } - - linkedPort := Port{ - Name: name, - Type: type_, - Default: def, - Source: source, - Iface: iface, - Feature: feature, + } + } +} + +func (iface *Interface) _createPort(which string, name string, config_ any) *Port { + var config *portFeature + var type_ reflect.Kind + var types_ []reflect.Kind + var feature int + + var def any + var qfunc func(*Port) + if reflect.TypeOf(config_) == reflectKind { + type_ = config_.(reflect.Kind) + + if type_ == types.Int { + def = 0 + } else if type_ == types.Bool { + def = false + } else if type_ == types.String { + def = "" + } else if type_ == types.Array { + def = [0]any{} // ToDo: is this actually working? + } else if type_ == types.Any { // Any + // pass + } else if type_ == types.Function { + // pass + } else if type_ == types.Route { + // pass + } else { + panic(iface.Namespace + ": '" + name + "' Port type(" + type_.String() + ") for initialization was not recognized") + } + } else { + config = config_.(*portFeature) + type_ = config.Type + feature = config.Id + + if feature == PortTypeTrigger { + qfunc = config.Func + type_ = types.Function + } else if feature == PortTypeArrayOf { + if type_ != types.Any { + def = &[]any{} } + } else if feature == PortTypeUnion { + types_ = config.Types + } else if feature == PortTypeDefault { + def = config.Value + } else { + // panic(iface.Namespace + ": '" + name + "' Port feature(" + strconv.Itoa(feature) + ") for initialization was not recognized") + } + } - ifacePort[name] = &linkedPort + var source int + if which == "Input" { + source = PortInput + } else if which == "Output" { + source = PortOutput + } + // else if which == "Property" { + // source = PortProperty + // } + + port := &Port{ + Name: name, + Type: type_, + Types: types_, + Default: def, + _func: qfunc, + Source: source, + Iface: iface, + Feature: feature, + _feature: config, + } - // CreateLinker() - if which == "Input" { - inputUpgradePort[name] = &PortInputGetterSetter{port: &linkedPort} - } else { - outputUpgradePort[name] = &PortOutputGetterSetter{port: &linkedPort} - } + return port +} + +func (iface *Interface) _initPortSwitches(portSwitches map[string]int) { + for key, val := range portSwitches { + if (val | 1) == 1 { + portStructOf_split(iface.Output[key]) } + + if (val | 2) == 2 { + iface.Output[key].AllowResync = true + } + } +} + +// Load saved port data value +func (iface *Interface) _importInputs(ports map[string]any) { + for key, val := range ports { + iface.Input[key].Default = val } } diff --git a/engine/node.go b/engine/node.go index 7a98941..fe0a082 100644 --- a/engine/node.go +++ b/engine/node.go @@ -4,38 +4,85 @@ import ( "github.com/blackprint/engine-go/utils" ) +type embedNode interface { + Init() + Request(*Cable) + Update(*Cable) + Imported(map[string]any) + Destroy() + SyncIn(id string, data ...any) +} + +type EmbedNode struct { + embedNode + Node *Node + Iface *Interface + Ref *referencesShortcut +} + +// To be overriden by module developer +func (n *EmbedNode) Init() {} +func (n *EmbedNode) Request(*Cable) {} +func (n *EmbedNode) Update(*Cable) {} +func (n *EmbedNode) Imported(map[string]any) {} +func (n *EmbedNode) Destroy() {} +func (n *EmbedNode) SyncIn(id string, data ...any) {} + type Node struct { - *customEvent - Instance *Instance - IFace any // any = extends *engine.Interface + Instance *Instance + Iface *Interface + DisablePorts bool + Routes *routePort + Embed embedNode + + Ref *referencesShortcut + + Output map[string]*portOutputGetterSetter + Input map[string]*portInputGetterSetter + // Property map[string]getterSetter + + // For internal library use only + _funcInstance *bpFunction + RefOutput map[string]*portOutputGetterSetter + // RefInput map[string]*portInputGetterSetter +} + +// Proxies, for library only +func (n *Node) Init() { n.Embed.Init() } +func (n *Node) Request(c *Cable) { n.Embed.Request(c) } +func (n *Node) Update(c *Cable) { n.Embed.Update(c) } +func (n *Node) Imported(d map[string]any) { n.Embed.Imported(d) } +func (n *Node) Destroy() { n.Embed.Destroy() } +func (n *Node) SyncIn(id string, data ...any) { n.Embed.SyncIn(id, data) } +type NodeRegister struct { // Port Template - TOutput map[string]any // any = port.Type or *port.Feature - TInput map[string]any // any = port.Type or *port.Feature - TProperty map[string]any // any = port.Type or *port.Feature + Output PortTemplate + Input PortTemplate + // Property *PortTemplate + + Constructor nodeConstructor +} - Output map[string]*PortOutputGetterSetter - Input map[string]*PortInputGetterSetter - // Property map[string]GetterSetter +type InterfaceRegister struct { + Constructor interfaceConstructor } -type NodeHandler func(*Instance) any // any = extends *engine.Node -type InterfaceHandler func(any) any // any = extends *engine.Node, *engine.Interface +type nodeConstructor func(*Node) +type interfaceConstructor func(*Interface) // QNodeList = Private function, for internal library only -var QNodeList = map[string]NodeHandler{} +var QNodeList = map[string]*NodeRegister{} // QInterfaceList = Private function, for internal library only -var QInterfaceList = map[string]InterfaceHandler{} +var QInterfaceList = map[string]*InterfaceRegister{} -// This will return *pointer -func (n *Node) SetInterface(namespace ...string) any { - if len(namespace) == 0 { - // Default interface (BP/Default) - iface := &Interface{QInitialized: true, Importing: true} +func (n *Node) SetInterface(namespace ...string) *Interface { + iface := &Interface{_initialized: true, Importing: true} - n.IFace = iface - n.customEvent = &customEvent{} + // Default interface (BP/Default) + if len(namespace) == 0 { + n.Iface = iface return iface } @@ -45,30 +92,97 @@ func (n *Node) SetInterface(namespace ...string) any { panic("Node interface for '" + name + "' was not found, maybe .registerInterface() haven't being called?") } - iface := class(n) - if utils.IsPointer(iface) == false { - panic(".registerInterface() must return pointer") + class.Constructor(iface) + for _, val := range iface.Data { + utils.SetProperty(val, "Iface", iface) } - data := utils.GetProperty(iface, "Data") - if data != nil { - _data := data.(InterfaceData) + iface._initialized = true + iface.Importing = true + n.Iface = iface - for _, port := range _data { - utils.SetProperty(port, "IFace", iface) - } + return iface +} + +func (n *Node) CreatePort(which string, name string, config_ any) *Port { + port := n.Iface._createPort(which, name, config_) + + if which != "input" { + ifacePort := n.Iface.Input + ifacePort[name] = port + n.Input[name] = &portInputGetterSetter{port: port} + } else if which != "output" { + ifacePort := n.Iface.Output + ifacePort[name] = port + n.Output[name] = &portOutputGetterSetter{port: port} + } else { + panic("Can only create port for 'input' and 'output'") } - utils.SetProperty(iface, "QInitialized", true) - utils.SetProperty(iface, "Importing", true) - n.IFace = iface - n.customEvent = &customEvent{} + return port +} - return iface +func (n *Node) RenamePort(which string, name string, to string) { + var portList map[string]*Port + if which == "input" { + portList = n.Iface.Input + } else if which == "output" { + portList = n.Iface.Output + } else { + panic("Can only rename port for 'input' and 'output'") + } + + port := portList[name] + if port == nil { + panic(which + " port with name '" + name + "' was not found") + } + + if portList[to] != nil { + panic(which + " port with name '" + to + "' already exist") + } + + portList[to] = port + delete(portList, name) + + port.Name = to + + if which == "input" { + n.Input[to] = n.Input[name] + delete(n.Input, name) + } else if which == "output" { + n.Output[to] = n.Output[name] + delete(n.Output, name) + } +} + +func (n *Node) DeletePort(which string, name string) { + var ports map[string]*Port + var port *Port + + if which != "input" { + ports = n.Iface.Input + port = ports[name] + if port == nil { + return + } + + delete(n.Input, name) + } else if which != "output" { + ports = n.Iface.Output + port = ports[name] + if port == nil { + return + } + + delete(n.Output, name) + } else { + panic("Can only delete port for 'input' and 'output'") + } + + port.DisconnectAll() + delete(ports, name) } -// To be overriden -func (n *Node) Init() {} -func (n *Node) Request(*Port, any) {} // any => extends engine.Interface -func (n *Node) Update(*Cable) {} -func (n *Node) Imported() {} +func (n *Node) Log(message string) { + n.Instance._log(n.Iface, message) +} diff --git a/engine/nodes/enums.go b/engine/nodes/enums.go new file mode 100644 index 0000000..2a409b4 --- /dev/null +++ b/engine/nodes/enums.go @@ -0,0 +1,13 @@ +package nodes + +const ( + BPEnvGet = iota + 1 + BPEnvSet + BPVarGet + BPVarSet + BPFnVarInput + BPFnVarOutput + BPFnInput + BPFnOutput + BPFnMain +) diff --git a/engine/nodesBPFunction.go b/engine/nodesBPFunction.go new file mode 100644 index 0000000..2dfee96 --- /dev/null +++ b/engine/nodesBPFunction.go @@ -0,0 +1,590 @@ +package engine + +import ( + "fmt" + "strconv" + "strings" + + "github.com/blackprint/engine-go/engine/nodes" + "github.com/blackprint/engine-go/types" + "github.com/blackprint/engine-go/utils" +) + +// Main function node +type bpFunctionNode struct { // Main function node -> BPI/F/{FunctionName} + *EmbedNode + Type string +} + +func (b *bpFunctionNode) Init() { + if b.Iface.Embed.(*FnMain)._importOnce { + b.Iface._bpFnInit() + } +} + +func (b *bpFunctionNode) Imported(data map[string]any) { + ins := b.Node._funcInstance + ins.Used = append(ins.Used, b.Node.Iface) +} + +func (b *bpFunctionNode) Update(cable *Cable) { + iface := b.Iface._proxyInput.Iface + Output := iface.Node.Output + + if cable == nil { // Triggered by port route + thisInput := b.Node.Input + + // Sync all port value + for key, value := range iface.Output { + if value.Type == types.Function { + continue + } + + Output[key].Set(thisInput[key].Get()) + } + + return + } + + // Update output value on the input node inside the function node + Output[cable.Input.Name].Set(cable.GetValue()) +} + +func (b *bpFunctionNode) Destroy() { + ins := b.Node._funcInstance + utils.RemoveItem(ins.Used, b.Node.Iface) +} + +// used for instance.createFunction +type bpFunction struct { // <= _funcInstance + *CustomEvent + Id string + Title string + Type int + Used []*Interface + Input PortTemplate + Output PortTemplate + Structure singleInstanceJSON + Variables map[string]*BPVariable + privateVars []string + RootInstance *Instance + Node func(*Instance) *Node // Node constructor + + // for internal library use only + _syncing bool +} + +func (b *bpFunction) _onFuncChanges(eventName string, obj any, fromNode *Node) { + for _, iface_ := range b.Used { + if iface_.Node == fromNode { + continue + } + + nodeInstance := iface_._bpInstance + // nodeInstance.PendingRender = true // Force recalculation for cable position + + if eventName == "cable.connect" || eventName == "cable.disconnect" { + cable := utils.GetProperty(obj, "Cable").(*Cable) + input := cable.Input + output := cable.Output + ifaceList := fromNode.Iface._bpInstance.IfaceList + + // Skip event that also triggered when deleting a node + if input.Iface._bpDestroy || output.Iface._bpDestroy { + continue + } + + inputIface := nodeInstance.IfaceList[utils.IndexOf(ifaceList, input.Iface)] + if inputIface == nil { + panic("Failed to get node input iface index") + } + + outputIface := nodeInstance.IfaceList[utils.IndexOf(ifaceList, output.Iface)] + if outputIface == nil { + panic("Failed to get node output iface index") + } + + if inputIface.Namespace != input.Iface.Namespace { + fmt.Println(inputIface.Namespace + " !== " + input.Iface.Namespace) + panic("Input iface namespace was different") + } + + if outputIface.Namespace != output.Iface.Namespace { + fmt.Println(outputIface.Namespace + " !== " + output.Iface.Namespace) + panic("Output iface namespace was different") + } + + if eventName == "cable.connect" { + targetInput := inputIface.Input[input.Name] + targetOutput := outputIface.Output[output.Name] + + if targetInput == nil { + if inputIface._enum == nodes.BPFnOutput { + targetInput = inputIface.Embed.(*qBpFnInOut).AddPort(targetOutput, output.Name) + } else { + panic("Output port was not found") + } + } + + if targetOutput == nil { + if outputIface._enum == nodes.BPFnInput { + targetOutput = outputIface.Embed.(*qBpFnInOut).AddPort(targetInput, input.Name) + } else { + panic("Input port was not found") + } + } + + targetInput.ConnectPort(targetOutput) + } else if eventName == "cable.disconnect" { + cables := inputIface.Input[input.Name].Cables + outputPort := outputIface.Output[output.Name] + + for _, cable := range cables { + if cable.Output == outputPort { + cable.Disconnect() + break + } + } + } + } else if eventName == "node.created" { + iface := utils.GetProperty(obj, "Iface").(*Interface) + nodeInstance.CreateNode(iface.Namespace, nodeConfig{ + Data: iface.Data, + }, nil) + } else if eventName == "node.delete" { + objIface := utils.GetProperty(obj, "Iface").(*Interface) + + index := utils.IndexOf(fromNode.Iface._bpInstance.IfaceList, objIface) + if index == -1 { + panic("Failed to get node index") + } + + iface := nodeInstance.IfaceList[index] + if iface.Namespace != objIface.Namespace { + fmt.Println(iface.Namespace + " " + objIface.Namespace) + panic("Failed to delete node from other function instance") + } + + if eventName == "node.delete" { + nodeInstance.DeleteNode(iface) + } + } + } +} + +// func (b *bpFunction) CreateNode(instance *Instance, options nodeConfig) (*Interface, []*Interface) { +// return instance.CreateNode(b.Node, options, nil) +// } + +type FnVarOptions struct { + Scope int +} + +func (b *bpFunction) CreateVariable(id string, options FnVarOptions) *BPVariable { + if _, exist := b.Variables[id]; exist { + panic("Variable id already exist: id") + } + + // deepProperty + + temp := &BPVariable{ + Id: id, + // options, + } + temp.FuncInstance = b + + if options.Scope == VarScopeShared { + b.Variables[id] = temp + } else { + b.AddPrivateVars(id) + return temp + } + + b.Emit("variable.new", temp) + b.RootInstance.Emit("variable.new", temp) + return temp +} + +type VariableNewEvent struct { + Id string + ScopeId int +} + +func (b *bpFunction) AddPrivateVars(id string) { + if utils.Contains(b.privateVars, id) { + return + } + + b.privateVars = append(b.privateVars, id) + + temp := &VariableNewEvent{ + ScopeId: VarScopePrivate, + Id: id, + } + b.Emit("variable.new", temp) + b.RootInstance.Emit("variable.new", temp) + + for _, iface := range b.Used { + iface._bpInstance.Variables[id] = &BPVariable{Id: id} + } +} + +func (b *bpFunction) RefreshPrivateVars(instance *Instance) { + vars := instance.Variables + for _, id := range b.privateVars { + vars[id] = &BPVariable{Id: id} + } +} + +func (b *bpFunction) RenamePort(which string, fromName string, toName string) { + var main PortTemplate + var proxyPort string + if which == "output" { + main = b.Output + proxyPort = "Input" + } else { + main = b.Input + proxyPort = "Output" + } + + main[toName] = main[fromName] + delete(main, fromName) + + for _, iface := range b.Used { + iface.Node.RenamePort(which, fromName, toName) + + var temp *Node + if which == "output" { + temp = iface._proxyOutput + } else { + temp = iface._proxyInput + } + + portList := utils.GetProperty(temp.Iface, proxyPort).(map[string]*Port) + portList[fromName].Name_.Name = toName + temp.RenamePort(proxyPort, fromName, toName) + + for _, proxyVar := range iface._bpInstance.IfaceList { + if (which == "output" && proxyVar.Namespace != "BP/FnVar/Output") || (which == "input" && proxyVar.Namespace != "BP/FnVar/Input") { + continue + } + + if proxyVar.Data["name"].Get() != fromName { + continue + } + proxyVar.Data["name"].Set(toName) + + if which == "output" { + proxyVar.Input["Val"].Name_.Name = toName + } + } + } +} + +func (b *bpFunction) Destroy() { + for _, iface := range b.Used { + iface.Node.Instance.DeleteNode(iface) + } +} + +type qNodeInput struct { + *EmbedNode +} + +func (n *qNodeInput) Imported(data map[string]any) { + input := n.Iface._funcMain.Node._funcInstance.Input + + for key, value := range input { + n.Node.CreatePort("output", key, value) + } +} + +func (n *qNodeInput) Request(cable *Cable) { + name := cable.Output.Name + + // This will trigger the port to request from outside and assign to this node's port + n.Node.Output[name].Set(n.Iface._funcMain.Node.Input[name].Get()) +} + +type qNodeOutput struct { + *EmbedNode +} + +func (n *qNodeOutput) Imported(data map[string]any) { + output := n.Iface._funcMain.Node._funcInstance.Output + + for key, value := range output { + n.Node.CreatePort("input", key, value) + } +} + +func (n *qNodeOutput) Update(cable *Cable) { + iface := n.Iface._funcMain + if cable == nil { // Triggered by port route + IOutput := iface.Output + Output := iface.Node.Output + thisInput := n.Node.Input + + // Sync all port value + for key, value := range IOutput { + if value.Type == types.Function { + continue + } + Output[key].Set(thisInput[key].Get()) + } + + return + } + + iface.Node.Output[cable.Input.Name].Set(cable.GetValue()) +} + +type FnMain struct { + *EmbedInterface + _importOnce bool + _save func(any, string, bool) + _portSw_ map[string]int +} + +func (f *FnMain) _bpFnInit() { + if f._importOnce { + panic("Can't import function more than once") + } + + f._importOnce = true + node := f.Node + + f.Iface._bpInstance = New() + bpFunction := node._funcInstance + + newInstance := f.Iface._bpInstance + // newInstance.Variables = []; // private for one function + newInstance.sharedVariables = bpFunction.Variables // shared between function + newInstance.Functions = node.Instance.Functions + newInstance._funcMain = f.Iface + newInstance._mainInstance = bpFunction.RootInstance + + bpFunction.RefreshPrivateVars(newInstance) + + // swallowCopy := make([]any, len(bpFunction.Structure)) + // copy(swallowCopy, bpFunction.Structure) + + f.Iface._bpInstance.importParsed(bpFunction.Structure) + + // Init port switches + if f._portSw_ != nil { + f.Iface._initPortSwitches(f._portSw_) + f._portSw_ = nil + + InputIface := f.Iface._proxyInput.Iface + InputIface_ := InputIface.Embed.(*qBpFnInOut) + + if InputIface_._portSw_ != nil { + InputIface._initPortSwitches(InputIface_._portSw_) + InputIface_._portSw_ = nil + } + } + + f._save = func(ev any, eventName string, force bool) { + if force || bpFunction._syncing { + return + } + + // ev.BpFunction = bpFunction + newInstance._mainInstance.Emit(eventName, ev) + + bpFunction._syncing = true + bpFunction._onFuncChanges(eventName, ev, f.Node) + bpFunction._syncing = false + } + + f.Iface._bpInstance.On("cable.connect cable.disconnect node.created node.delete node.id.changed", f._save) +} +func (f *FnMain) RenamePort(which string, fromName string, toName string) { + f.Node._funcInstance.RenamePort(which, fromName, toName) + f._save(false, "", true) +} + +type qBpFnInOut struct { + *EmbedInterface + Type string + _portSw_ map[string]int +} + +type addPortRef struct { + Node *Node + Port *Port +} + +func (b *qBpFnInOut) AddPort(port *Port, customName string) *Port { + if port == nil { + panic("Can't set type with nil") + } + + if strings.HasPrefix(port.Iface.Namespace, "BP/Fn") { + panic("Function Input can't be connected directly to Output") + } + + var name string + if port.Name_ != nil { + name = port.Name_.Name + } else if customName != "" { + name = customName + } else { + name = port.Name + } + + var reff *addPortRef + var portType any + if port.Feature == PortTypeTrigger { + reff = &addPortRef{} + portType = QPorts.Trigger(func(*Port) { + reff.Node.Output[reff.Port.Name].Call() + }) + } else { + if port.Feature != 0 { + portType = port._getPortFeature() + } else { + portType = port.Type + } + } + + var nodeA *Node + var nodeB *Node + // nodeA, nodeB; // Main (input) -> Input (output), Output (input) -> Main (output) + if b.Type == "bp-fn-input" { // Main (input) -> Input (output) + inc := 1 + for true { + _, exist := b.Iface.Output[name] + if !exist { + break + } + + name += strconv.Itoa(inc) + inc++ + } + + nodeA = b.Iface._funcMain.Node + nodeB = b.Node + nodeA._funcInstance.Input[name] = portType + } else { // Output (input) -> Main (output) + inc := 1 + for true { + _, exist := b.Iface.Input[name] + if !exist { + break + } + + name += strconv.Itoa(inc) + inc++ + } + + nodeA = b.Node + nodeB = b.Iface._funcMain.Node + nodeB._funcInstance.Output[name] = portType + } + + outputPort := nodeB.CreatePort("output", name, portType) + + var inputPort *Port + if portType == types.Function { + inputPort = nodeA.CreatePort("input", name, QPorts.Trigger(outputPort._callAll)) + } else { + inputPort = nodeA.CreatePort("input", name, portType) + } + + if reff != nil { + reff.Node = nodeB + reff.Port = inputPort + } + + if b.Type == "bp-fn-input" { + outputPort.Name_ = &refPortName{Name: name} // When renaming port, this also need to be changed + b.Iface.Emit("_add.{name}", outputPort) + + inputPort.On("value", func(ev PortValueEvent) { + outputPort.Iface.Node.Output[outputPort.Name].Set(ev.Cable.Output.Value) + }) + + return outputPort + } + + inputPort.Name_ = &refPortName{Name: name} // When renaming port, this also need to be changed + b.Iface.Emit("_add.{name}", inputPort) + return inputPort +} + +func (b *qBpFnInOut) RenamePort(fromName string, toName string) { + bpFunction := b.Iface._funcMain.Node._funcInstance + // Main (input) -> Input (output) + if b.Type == "bp-fn-input" { + bpFunction.RenamePort("input", fromName, toName) + } else { // Output (input) -> Main (output) + bpFunction.RenamePort("output", fromName, toName) + } +} + +func (b *qBpFnInOut) DeletePort(name string) { + funcMainNode := b.Iface._funcMain.Node + if b.Type == "bp-fn-input" { // Main (input) -> Input (output) + funcMainNode.DeletePort("input", name) + b.Node.DeletePort("output", name) + delete(funcMainNode._funcInstance.Input, name) + } else { // Output (input) -> Main (output) + funcMainNode.DeletePort("output", name) + b.Node.DeletePort("input", name) + delete(funcMainNode._funcInstance.Output, name) + } +} + +func init() { + QNodeList["BP/Fn/Input"] = &NodeRegister{ + Output: PortTemplate{}, + Constructor: func(node *Node) { + node.Embed = &qNodeInput{} + + iface := node.SetInterface("BPIC/BP/Fn/Input") + iface._enum = nodes.BPFnInput + iface._dynamicPort = true // Port is initialized dynamically + + iface.Title = "Input" + iface.Embed.(*qBpFnInOut).Type = "bp-fn-input" + iface._funcMain = node.Instance._funcMain + iface._funcMain._proxyInput = node + }, + } + + QInterfaceList["BPIC/BP/Fn/Input"] = &InterfaceRegister{ + Constructor: func(iface *Interface) { + iface.Embed = &qBpFnInOut{} + }, + } + + QNodeList["BP/Fn/Output"] = &NodeRegister{ + Input: PortTemplate{}, + Constructor: func(node *Node) { + node.Embed = &bpVarGet{} + + iface := node.SetInterface("BPIC/BP/Fn/Output") + iface._enum = nodes.BPFnOutput + iface._dynamicPort = true // Port is initialized dynamically + + iface.Title = "Output" + iface.Embed.(*qBpFnInOut).Type = "bp-fn-output" + iface._funcMain = node.Instance._funcMain + iface._funcMain._proxyOutput = node + }, + } + + QInterfaceList["BPIC/BP/Fn/Output"] = &InterfaceRegister{ + Constructor: func(iface *Interface) { + iface.Embed = &qBpFnInOut{} + }, + } + + QInterfaceList["BPIC/BP/Fn/Main"] = &InterfaceRegister{ + Constructor: func(iface *Interface) { + iface.Embed = &FnMain{} + }, + } +} diff --git a/engine/nodesBPVariable.go b/engine/nodesBPVariable.go new file mode 100644 index 0000000..773f747 --- /dev/null +++ b/engine/nodesBPVariable.go @@ -0,0 +1,300 @@ +package engine + +import ( + "strconv" + + "github.com/blackprint/engine-go/engine/nodes" + "github.com/blackprint/engine-go/types" + "github.com/blackprint/engine-go/utils" +) + +type bpVarSet struct { + *EmbedNode +} +type bpVarGet struct { + *EmbedNode +} + +func (b *bpVarSet) Update(c *Cable) { + b.Iface._bpVarRef.Value.Set(b.Node.Input["Val"].Get()) +} + +type bpVarGetSet struct { + *EmbedInterface + bpVarGetSetIFace + Type string + _bpVarRef *BPVariable + _onChanged func(*Port) +} + +type bpVarGetSetIFace interface { + _reinitPort() *Port +} + +func (b *bpVarGetSet) Imported(data map[string]any) { + if _, exist := data["scope"]; exist { + panic("'scope' options is required for creating variable node") + } + + if _, exist := data["name"]; exist { + panic("'name' options is required for creating variable node") + } + + b.ChangeVar(data["name"].(string), data["scope"].(int)) + b._bpVarRef.Used = append(b._bpVarRef.Used, b.Iface) +} + +func (b *bpVarGetSet) ChangeVar(name string, scopeId int) map[string]*BPVariable { + if _, exist := b.Iface.Data["name"]; exist { + panic("Can't change variable node that already be initialized") + } + + b.Iface.Data["name"] = &GetterSetter{Value: name} + b.Iface.Data["scope"] = &GetterSetter{Value: scopeId} + + thisInstance := b.Node.Instance + funcInstance := thisInstance._funcMain + var bpFunc *bpFunction + if funcInstance != nil { + bpFunc = funcInstance.Node._funcInstance + } + + var scope map[string]*BPVariable + if scopeId == VarScopePublic { + if funcInstance != nil { + scope = bpFunc.RootInstance.Variables + } else { + scope = thisInstance.Variables + } + } else if scopeId == VarScopeShared { + scope = bpFunc.Variables + } else { // private + scope = thisInstance.Variables + } + + if _, exist := scope[name]; !exist { + var scopeName string + if scopeId == VarScopePublic { + scopeName = "public" + } else if scopeId == VarScopePrivate { + scopeName = "private" + } else if scopeId == VarScopeShared { + scopeName = "shared" + } else { + scopeName = "unknown" + } + + panic("'" + name + "' variable was not defined on the '" + scopeName + " (scopeId: " + strconv.Itoa(scopeId) + ")' instance") + } + + return scope +} + +func (b *bpVarGetSet) UseType(port *Port) bool { + if b._bpVarRef.Type != 0 { // Type was set + if port == nil { + b._bpVarRef.Type = 0 // Type not set + } + return true + } + + if port == nil { + panic("Can't set type with null") + } + + return false +} + +func (b *bpVarGetSet) UseType_(port *Port, targetPort *Port) { + b._bpVarRef.Type = port.Type + targetPort.ConnectPort(port) + + // Also create port for other node that using $this variable + for _, item := range b._bpVarRef.Used { + item.Embed.(bpVarGetSetIFace)._reinitPort() + } +} + +func (b *bpVarGetSet) Destroy() { + temp := b._bpVarRef + if temp == nil { + return + } + + temp.Used = utils.RemoveItem(temp.Used, b.Iface) + + listener := b._bpVarRef.Listener + if listener == nil { + return + } + + b._bpVarRef.Listener = utils.RemoveItem(listener, b.Iface) +} + +type iVarSet struct { + *bpVarGetSet + _eventListen string +} + +func (b *iVarSet) UseType(port *Port) { + if !b.bpVarGetSet.UseType(port) { + b.bpVarGetSet.UseType_(port, b._reinitPort()) + } +} + +func (b *iVarSet) ChangeVar(name string, scopeId int) { + if _, exist := b.Iface.Data["name"]; exist { + panic("Can't change variable node that already be initialized") + } + + if b._onChanged != nil && b._bpVarRef != nil { + b._bpVarRef.Off("value", b._onChanged) + } + + scope := b.bpVarGetSet.ChangeVar(name, scopeId) + b.Iface.Title = "Get " + name + + temp := scope[b.Iface.Data["name"].Get().(string)] + b._bpVarRef = temp + if temp.Type == 0 { // Type not set + return + } + + b._reinitPort() +} + +func (b *iVarSet) _reinitPort() *Port { + temp := b._bpVarRef + node := b.Node + + if b.Iface.Output["Val"] != nil { + node.DeletePort("output", "Val") + } + + ref := b.Node.Output + b.Node.CreatePort("output", "Val", temp.Type) + + if temp.Type == types.Function { + b._eventListen = "call" + b._onChanged = func(p *Port) { + ref["Val"].Call() + } + } else { + b._eventListen = "value" + b._onChanged = func(p *Port) { + ref["Val"].Set(temp.Value.Get()) + } + } + + temp.On(b._eventListen, b._onChanged) + return b.Iface.Output["Val"] +} + +func (b *iVarSet) Destroy() { + if b._eventListen != "" { + b._bpVarRef.Off(b._eventListen, b._onChanged) + } + + b.bpVarGetSet.Destroy() +} + +type iVarGet struct { + *bpVarGetSet +} + +func (b *iVarGet) UseType(port *Port) { + if !b.bpVarGetSet.UseType(port) { + b.bpVarGetSet.UseType_(port, b._reinitPort()) + } +} + +func (b *iVarGet) ChangeVar(name string, scopeId int) { + scope := b.bpVarGetSet.ChangeVar(name, scopeId) + b.Iface.Title = "Set " + name + + temp := scope[b.Iface.Data["name"].Get().(string)] + b._bpVarRef = temp + if temp.Type == 0 { // Type not set + return + } + + b._reinitPort() +} + +func (b *iVarGet) _reinitPort() *Port { + input := b.Iface.Input + node := b.Node + temp := b._bpVarRef + + if _, exist := input["Val"]; exist { + node.DeletePort("Input", "Val") + } + + if temp.Type == types.Function { + node.CreatePort("Input", "Val", QPorts.Trigger(func(p *Port) { + temp.Emit("call", nil) + })) + } else { + node.CreatePort("Input", "Val", temp.Type) + } + + return input["Val"] +} + +func init() { + QNodeList["BP/Var/Set"] = &NodeRegister{ + Input: PortTemplate{}, + Constructor: func(node *Node) { + node.Embed = &bpVarSet{} + + iface := node.SetInterface("BPIC/BP/Var/Set") + + // Specify data field from here to make it enumerable and exportable + iface.Data = InterfaceData{ + "name": &GetterSetter{Value: ""}, + "scope": &GetterSetter{Value: VarScopePublic}, + } + + iface.Title = "VarSet" + iface.Embed.(*iVarSet).Type = "bp-var-set" + iface._enum = nodes.BPVarSet + iface._dynamicPort = true + }, + } + + QInterfaceList["BPIC/BP/Var/Set"] = &InterfaceRegister{ + Constructor: func(iface *Interface) { + iface.Embed = &iVarSet{ + bpVarGetSet: &bpVarGetSet{}, + } + }, + } + + QNodeList["BP/Var/Get"] = &NodeRegister{ + Output: PortTemplate{}, + Constructor: func(node *Node) { + node.Embed = &bpVarGet{} + + iface := node.SetInterface("BPIC/BP/Var/Get") + + // Specify data field from here to make it enumerable and exportable + iface.Data = InterfaceData{ + "name": &GetterSetter{Value: ""}, + "scope": &GetterSetter{Value: VarScopePublic}, + } + + iface.Title = "VarGet" + iface.Embed.(*iVarGet).Type = "bp-var-get" + iface._enum = nodes.BPVarGet + iface._dynamicPort = true + }, + } + + QInterfaceList["BPIC/BP/Var/Get"] = &InterfaceRegister{ + Constructor: func(iface *Interface) { + iface.Embed = &iVarGet{ + bpVarGetSet: &bpVarGetSet{}, + } + }, + } +} diff --git a/engine/nodesEnvironment.go b/engine/nodesEnvironment.go new file mode 100644 index 0000000..e06c3b6 --- /dev/null +++ b/engine/nodesEnvironment.go @@ -0,0 +1,127 @@ +package engine + +import ( + "github.com/blackprint/engine-go/engine/nodes" + "github.com/blackprint/engine-go/types" +) + +type bpEnvGet struct { + *EmbedNode +} + +type bpEnvSet struct { + *EmbedNode +} + +func (b *bpEnvSet) Update(c *Cable) { + QEnvironment.Set(b.Iface.Data["name"].Get().(string), c.GetValue().(string)) +} + +type bpEnvGetSet struct { + *EmbedInterface + Type string +} + +func (b *bpEnvGetSet) Imported(data map[string]any) { + if data["name"] == nil { + panic("Parameter 'name' is required") + } + + b.Iface.Data["name"].Set(data["name"]) + name := data["name"].(string) + + if _, exists := QEnvironment.Map[name]; !exists { + QEnvironment.Set(name, "") + } +} + +type iEnvGet struct { + *bpEnvGetSet + _listener func(any) +} + +func (b *iEnvGet) Imported(data map[string]any) { + b.bpEnvGetSet.Imported(data) + + b._listener = func(v any) { + ev := v.(*EnvironmentEvent) + if ev.Key != b.Iface.Data["name"].Get().(string) { + return + } + + b.Ref.Output["Val"].Set(ev.Value) + } + + Event.On("environment.changed environment.added", b._listener) + b.Ref.Output["Val"].Set(QEnvironment.Map[b.Iface.Data["name"].Get().(string)]) +} + +func (b *iEnvGet) Destroy() { + if b._listener == nil { + return + } + + Event.Off("environment.changed environment.added", b._listener) +} + +type iEnvSet struct { + *bpEnvGetSet +} + +func init() { + QNodeList["BP/Env/Get"] = &NodeRegister{ + Output: PortTemplate{ + "Val": types.String, + }, + Constructor: func(node *Node) { + node.Embed = &bpEnvGet{} + + iface := node.SetInterface("BPIC/BP/Env/Get") + + // Specify data field from here to make it enumerable and exportable + iface.Data = InterfaceData{ + "name": &GetterSetter{Value: ""}, + } + + iface.Title = "EnvGet" + iface.Embed.(*iEnvGet).Type = "bp-env-get" + iface._enum = nodes.BPEnvGet + }, + } + + QInterfaceList["BPIC/BP/Env/Get"] = &InterfaceRegister{ + Constructor: func(iface *Interface) { + iface.Embed = &iEnvGet{ + bpEnvGetSet: &bpEnvGetSet{}, + } + }, + } + + QNodeList["BP/Env/Set"] = &NodeRegister{ + Input: PortTemplate{ + "Val": types.String, + }, + Constructor: func(node *Node) { + node.Embed = &bpEnvSet{} + + iface := node.SetInterface("BPIC/BP/Env/Set") + + // Specify data field from here to make it enumerable and exportable + iface.Data = InterfaceData{ + "name": &GetterSetter{Value: ""}, + } + + iface.Title = "EnvSet" + iface.Embed.(*iEnvSet).Type = "bp-env-set" + iface._enum = nodes.BPEnvSet + }, + } + + QInterfaceList["BPIC/BP/Env/Set"] = &InterfaceRegister{ + Constructor: func(iface *Interface) { + iface.Embed = &iEnvSet{ + bpEnvGetSet: &bpEnvGetSet{}, + } + }, + } +} diff --git a/engine/nodesFnPortVar.go b/engine/nodesFnPortVar.go new file mode 100644 index 0000000..22a1943 --- /dev/null +++ b/engine/nodesFnPortVar.go @@ -0,0 +1,308 @@ +package engine + +import ( + "github.com/blackprint/engine-go/engine/nodes" + "github.com/blackprint/engine-go/types" +) + +type fnVarInput struct { + *EmbedNode +} + +func (f *fnVarInput) Imported(data map[string]any) { + if f.Node.Routes != nil { + f.Node.Routes.Disabled = true + } +} + +func (f *fnVarInput) Request(cable *Cable) { + iface := f.Iface + + // This will trigger the port to request from outside and assign to this node's port + f.Node.Output["Val"].Set(iface._parentFunc.Node.Input[iface.Data["name"].Get().(string)]) +} + +type fnVarOutput struct { + *EmbedNode +} + +func (f *fnVarOutput) Update(c *Cable) { + id := f.Iface.Data["name"].Get().(string) + f.Node.RefOutput[id].Set(f.Ref.Input["Val"].Get()) +} + +type bpFnVarInOut struct { + *EmbedInterface + _onConnect func(*Cable, *Port) + + _parentFunc *Interface + _proxyIface *Interface + _listener func(any) + _waitPortInit func(*Port) + Type string +} + +func (f *bpFnVarInOut) Imported(data map[string]any) { + if data["name"] == nil { + panic("Parameter 'name' is required") + } + + f.Iface.Data["name"].Set(data["name"]) + f._parentFunc = f.Node.Instance._funcMain +} + +type fnVarInputIface struct { + *bpFnVarInOut +} + +func (f *fnVarInputIface) Imported(data map[string]any) { + f.bpFnVarInOut.Imported(data) + ports := f._parentFunc.Ref.IInput + node := f.Node + + f._proxyIface = f._parentFunc._proxyInput.Iface + + // Create temporary port if the main function doesn't have the port + name := data["name"].(string) + if _, exist := ports[name]; !exist { + iPort := node.CreatePort("input", "Val", types.Any) + proxyIface := f._proxyIface + + // Run when $this node is being connected with other node + iPort._onConnect = func(cable *Cable, port *Port) bool { + iPort._onConnect = nil + proxyIface.Off("_add."+name, iPort._waitPortInit) + iPort._waitPortInit = nil + + cable.Disconnect() + node.DeletePort("output", "Val") + + portName := &refPortName{Name: name} + portType := getFnPortType(port, "input", f._parentFunc, portName) + newPort := node.CreatePort("output", "Val", portType) + newPort.Name_ = portName + newPort.ConnectPort(port) + + proxyIface.Embed.(*qBpFnInOut).AddPort(port, name) + f._addListener() + + return true + } + + // Run when main node is the missing port + iPort._waitPortInit = func(port *Port) { + iPort._onConnect = nil + iPort._waitPortInit = nil + + backup := []*Port{} + for _, val := range f.Iface.Output["Val"].Cables { + backup = append(backup, val.Input) + } + + node := f.Node + node.DeletePort("output", "Val") + + portType := getFnPortType(port, "input", f._parentFunc, port.Name_) + newPort := node.CreatePort("output", "Val", portType) + f._addListener() + + for _, val := range backup { + newPort.ConnectPort(val) + } + } + + proxyIface.Once("_add."+name, iPort._waitPortInit) + } else { + if _, exist := f.Iface.Output["Val"]; !exist { + port := ports[name] + portType := getFnPortType(port, "input", f._parentFunc, port.Name_) + node.CreatePort("input", "Val", portType) + } + + f._addListener() + } +} + +func (f *fnVarInputIface) _addListener() { + port := f._proxyIface.Output[f.Iface.Data["name"].Get().(string)] + + if port.Feature == PortTypeTrigger { + f._listener = func(p any) { + f.Ref.Output["Val"].Call() + } + + port.On("call", f._listener) + } else { + f._listener = func(ev any) { + port := ev.(*PortValueEvent).Port + if port.Iface.Node.Routes.Out == nil { + val := f.Ref.IOutput["Val"] + val.Value = port.Value // Change value without trigger node.update + + for _, temp := range val.Cables { + // Clear connected cable's cache + temp.Input._cache = nil + } + return + } + + f.Ref.Output["Val"].Set(port.Value) + } + + port.On("value", f._listener) + } +} + +func (f *fnVarInputIface) Destroy() { + f.bpFnVarInOut.Destroy() + + if f._listener == nil { + return + } + + port := f._proxyIface.Output[f.Iface.Data["name"].Get().(string)] + if port.Feature == PortTypeTrigger { + port.Off("call", f._listener) + } else { + port.Off("value", f._listener) + } +} + +type fnVarOutputIface struct { + *bpFnVarInOut +} + +func (f *fnVarOutputIface) Imported(data map[string]any) { + f.bpFnVarInOut.Imported(data) + ports := f._parentFunc.Ref.IOutput + node := f.Node + + node.RefOutput = f._parentFunc.Ref.Output + + // Create temporary port if the main function doesn't have the port + name := data["name"].(string) + if _, exist := ports[name]; !exist { + iPort := node.CreatePort("input", "Val", types.Any) + proxyIface := f._parentFunc._proxyOutput.Iface + + // Run when $this node is being connected with other node + iPort._onConnect = func(cable *Cable, port *Port) bool { + iPort._onConnect = nil + proxyIface.Off("_add."+name, iPort._waitPortInit) + iPort._waitPortInit = nil + + cable.Disconnect() + node.DeletePort("input", "Val") + + portName := &refPortName{Name: name} + portType := getFnPortType(port, "output", f._parentFunc, portName) + newPort := node.CreatePort("input", "Val", portType) + newPort.Name_ = portName + newPort.ConnectPort(port) + + proxyIface.Embed.(*qBpFnInOut).AddPort(port, name) + return true + } + + // Run when main node is the missing port + iPort._waitPortInit = func(port *Port) { + iPort._onConnect = nil + iPort._waitPortInit = nil + + backup := []*Port{} + for _, val := range f.Iface.Output["Val"].Cables { + backup = append(backup, val.Input) + } + + node := f.Node + node.DeletePort("input", "Val") + + portType := getFnPortType(port, "output", f._parentFunc, port.Name_) + newPort := node.CreatePort("input", "Val", portType) + + for _, val := range backup { + newPort.ConnectPort(val) + } + } + + proxyIface.Once("_add."+name, iPort._waitPortInit) + } else { + if _, exist := f.Iface.Output["Val"]; !exist { + port := ports[name] + portType := getFnPortType(port, "output", f._parentFunc, port.Name_) + node.CreatePort("input", "Val", portType) + } + } +} + +func getFnPortType(port *Port, which string, parentNode *Interface, ref *refPortName) any { + if port.Feature == PortTypeTrigger { + if which == "input" { // Function Input (has output port inside, and input port on main node) + return types.Function + } else { + return QPorts.Trigger(parentNode.Output[ref.Name]._callAll) + } + } else { + if port.Feature != 0 { + return port._getPortFeature() + } else { + return port.Type + } + } +} + +func init() { + QNodeList["BP/FnVar/Input"] = &NodeRegister{ + Output: PortTemplate{}, + Constructor: func(node *Node) { + node.Embed = &fnVarInput{} + + iface := node.SetInterface("BPIC/BP/FnVar/Input") + + // Specify data field from here to make it enumerable and exportable + iface.Data = InterfaceData{ + "name": &GetterSetter{Value: ""}, + } + + iface.Title = "FnInput" + iface.Embed.(*fnVarInputIface).Type = "bp-fnvar-input" + iface._enum = nodes.BPFnVarInput + iface._dynamicPort = true + }, + } + + QInterfaceList["BPIC/BP/FnVar/Input"] = &InterfaceRegister{ + Constructor: func(iface *Interface) { + iface.Embed = &fnVarInputIface{ + bpFnVarInOut: &bpFnVarInOut{}, + } + }, + } + + QNodeList["BP/FnVar/Output"] = &NodeRegister{ + Input: PortTemplate{}, + Constructor: func(node *Node) { + node.Embed = &fnVarOutput{} + + iface := node.SetInterface("BPIC/BP/FnVar/Output") + + // Specify data field from here to make it enumerable and exportable + iface.Data = InterfaceData{ + "name": &GetterSetter{Value: ""}, + } + + iface.Title = "FnOutput" + iface.Embed.(*fnVarOutputIface).Type = "bp-fnvar-output" + iface._enum = nodes.BPFnVarOutput + iface._dynamicPort = true + }, + } + + QInterfaceList["BPIC/BP/FnVar/Output"] = &InterfaceRegister{ + Constructor: func(iface *Interface) { + iface.Embed = &fnVarOutputIface{ + bpFnVarInOut: &bpFnVarInOut{}, + } + }, + } +} diff --git a/engine/port.go b/engine/port.go index 2d8238e..c65d1c0 100644 --- a/engine/port.go +++ b/engine/port.go @@ -1,234 +1,355 @@ package engine import ( + "fmt" "reflect" + "github.com/blackprint/engine-go/engine/nodes" + "github.com/blackprint/engine-go/types" "github.com/blackprint/engine-go/utils" ) +type PortStructTemplate struct { + Type any + Field string + Handle func(any) any +} + type Port struct { - customEvent - Name string - Type reflect.Kind - Cables []*Cable - Source int - Iface *Interface - Default any // Dynamic data (depend on Type) for storing port value (int, string, map, etc..) - Value any // Dynamic data (depend on Type) for storing port value (int, string, map, etc..) - Func func(any) - Sync bool - Feature int + CustomEvent + Name string + Name_ *refPortName // For bpFunction only, ToDo: fill alternate name, search in engine-php _name for hints + Type reflect.Kind + Types []reflect.Kind + Cables []*Cable + Source int + Iface *Interface + Default any // Dynamic data (depend on Type) for storing port value (int, string, map, etc..) + Value any // Dynamic data (depend on Type) for storing port value (int, string, map, etc..) + Sync bool + Feature int + _feature *portFeature // For caching the configuration + Struct map[string]PortStructTemplate + Splitted bool + AllowResync bool // Retrigger connected node's .update when the output value is similar + + // Only in Golang we need to do this '-' + RoutePort *routePort + + // Internal/Private property + _cache any + _parent *Port + _structSplitted bool + _ghost bool + _func func(*Port) + _callAll func(*Port) + _onConnect func(*Cable, *Port) bool + _waitPortInit func(*Port) +} + +/** For internal library use only */ +// For bpFunction only +type refPortName struct { + Name string } const ( PortInput = iota + 1 PortOutput - PortProperty + // PortProperty ) const ( PortTypeArrayOf = iota + 1 PortTypeDefault - PortTypeSwitch PortTypeTrigger PortTypeUnion - PortTypeValidator + PortTypeStructOf + PortTypeRoute ) // Port feature -type PortFeature struct { +type portFeature struct { Id int Type reflect.Kind Types []reflect.Kind Value any - Func any + Func func(*Port) } -type PortInputGetterSetter struct { - GetterSetter - port *Port -} - -func (gs *PortInputGetterSetter) Set(val any) { - panic("Can't set input port's value") +func (port *Port) _getPortFeature() *portFeature { + return port._feature } +func (port *Port) DisconnectAll() { + hasRemote := port.Iface.Node.Instance._remote == nil + for _, cable := range port.Cables { + if hasRemote { + cable._evDisconnected = true + } -func (gs *PortInputGetterSetter) Call() { - gs.port.Default.(func(*Port))(gs.port) + cable.Disconnect() + } } -func (gs *PortInputGetterSetter) Get() any { - port := gs.port +// ./portGetterSetter.go +func (port *Port) CreateLinker() getterSetter { + if port.Source == PortInput { + return &portInputGetterSetter{port: port} + } - // This port must use values from connected output - cableLen := len(port.Cables) + return &portOutputGetterSetter{port: port} +} - if cableLen == 0 { - if port.Feature == PortTypeArrayOf { - // ToDo: fix type to follow - // the type from port.Type +func (port *Port) sync() { + skipSync := port.Iface.Node.Routes.Out != nil - return [](any){} + for _, cable := range port.Cables { + inp := cable.Input + if inp == nil { + continue } - return port.Default - } - - // Flag current iface is requesting value to other iface + inp._cache = nil - // Return single data - if cableLen == 1 { - temp := port.Cables[0] - var target *Port - - if temp.Owner == port { - target = temp.Target - } else { - target = temp.Owner + temp := &PortValueEvent{ + Target: inp, + Port: port, + Cable: cable, } + inpIface := inp.Iface + + inp.Emit("value", temp) + inpIface.Emit("value", temp) - if target.Value == nil { - port.Iface.QRequesting = true - utils.CallFunction(target.Iface.Node, "Request", &[]reflect.Value{ - reflect.ValueOf(target), - reflect.ValueOf(port.Iface), - }) - port.Iface.QRequesting = false + if skipSync { + continue } - // fmt.Printf("1. %s -> %s (%s)\n", port.Name, target.Name, target.Value) + node := inpIface.Node + if inpIface._requesting == false && len(node.Routes.In) == 0 { + node.Update(cable) - if port.Feature == PortTypeArrayOf { - var tempVal any - if target.Value == nil { - tempVal = target.Default + if inpIface._enum == nodes.BPFnMain { + node.Routes.RouteOut() } else { - tempVal = target.Value + inpIface._proxyInput.Routes.RouteOut() } - - return [](any){tempVal} - } - - if target.Value == nil { - return target.Default - } else { - return target.Value } } +} - // Return multiple data as an array - data := []any{} - for _, cable := range port.Cables { - var target *Port - if cable.Owner == port { - target = cable.Target - } else { - target = cable.Owner +func (port *Port) DisableCables(enable bool) { + if enable { + for _, cable := range port.Cables { + cable.Disabled = 1 } - - if target.Value == nil { - port.Iface.QRequesting = true - utils.CallFunction(target.Iface.Node, "Request", &[]reflect.Value{ - reflect.ValueOf(target), - reflect.ValueOf(port.Iface), - }) - port.Iface.QRequesting = false + } else if !enable { + for _, cable := range port.Cables { + cable.Disabled = 0 } + } else { + panic("Unhandled, please check engine-php's implementation") + } +} - // fmt.Printf("2. %s -> %s (%s)\n", port.Name, target.Name, target.Value) +type CableErrorEvent struct { + Cable *Cable + OldCable *Cable + Iface *Interface + Port *Port + Target *Port + Message string +} - if target.Value == nil { - data = append(data, target.Default) - } else { - data = append(data, target.Value) - } +func (port *Port) _cableConnectError(name string, obj *CableErrorEvent, severe bool) { + msg := "Cable notify: " + name + if obj.Iface != nil { + msg += "\nIFace: " + obj.Iface.Namespace } - if port.Feature != PortTypeArrayOf { - return data[0] + if obj.Port != nil { + msg += fmt.Sprintf("\nFrom port: %s (iface: %s)\n - Type: %d) (%d)", obj.Port.Name, obj.Port.Iface.Namespace, obj.Port.Source, obj.Port.Type) } - return data -} + if obj.Target != nil { + msg += fmt.Sprintf("\nTo port: %s (iface: %s)\n - Type: %d) (%d)", obj.Target.Name, obj.Target.Iface.Namespace, obj.Target.Source, obj.Target.Type) + } -type PortOutputGetterSetter struct { - GetterSetter - port *Port + obj.Message = msg + instance := port.Iface.Node.Instance + + if severe && instance.PanicOnError { + panic(msg + "\n\n") + } + + instance.Emit(name, obj) } +func (port *Port) ConnectCable(cable *Cable) bool { + if cable.IsRoute { + port._cableConnectError("cable.not_route_port", &CableErrorEvent{ + Cable: cable, + Port: port, + Target: cable.Owner, + }, true) + + cable.Disconnect() + return false + } -func (gs *PortOutputGetterSetter) Set(val any) { - port := gs.port + if cable.Owner == port { // It's referencing to same port + cable.Disconnect() + return false + } - if port.Source == PortInput { - panic("Can't set data to input port") + if (port._onConnect != nil && port._onConnect(cable, cable.Owner)) || (cable.Owner._onConnect != nil && cable.Owner._onConnect(cable, port)) { + return false } - // ToDo: do we need feature validation here? - // fmt.Printf("3. %s = %s\n", port.Name, val) + // Remove cable if ... + if (cable.Source == PortOutput && port.Source != PortInput) /* Output source not connected to input */ || (cable.Source == PortInput && port.Source != PortOutput) /* Input source not connected to output */ { + port._cableConnectError("cable.wrong_pair", &CableErrorEvent{ + Cable: cable, + Port: port, + Target: cable.Owner, + }, true) - port.Value = val - port.QTrigger("value", port) - port.sync() -} + cable.Disconnect() + return false + } -func (gs *PortOutputGetterSetter) Call() { - var target *Port - for _, cable := range gs.port.Cables { - if cable.Owner == gs.port { - target = cable.Target - } else { - target = cable.Owner + if cable.Owner.Source == PortOutput { + if (port.Feature == PortTypeArrayOf && !portArrayOf_validate(port, cable.Owner)) || (port.Feature == PortTypeUnion && !portUnion_validate(port, cable.Owner)) { + port._cableConnectError("cable.wrong_type", &CableErrorEvent{ + Cable: cable, + Iface: port.Iface, + Port: cable.Owner, + Target: port, + }, true) + + cable.Disconnect() + return false + } + } else if port.Source == PortOutput { + if (cable.Owner.Feature == PortTypeArrayOf && !portArrayOf_validate(cable.Owner, port)) || (cable.Owner.Feature == PortTypeUnion && !portUnion_validate(cable.Owner, port)) { + port._cableConnectError("cable.wrong_type", &CableErrorEvent{ + Cable: cable, + Iface: port.Iface, + Port: port, + Target: cable.Owner, + }, true) + + cable.Disconnect() + return false } + } - // fmt.Println(cable.String()) - target.Default.(func(*Port))(target) + // Golang can't check by class instance or inheritance + // ========================================== + // ToDo: recheck why we need to check if the constructor is a function + // isInstance = true + // if cable.Owner.Type != port.Type && cable.Owner.Type == types.Function && port.Type == types.Function { + // if cable.Owner.Source == PortOutput{ + // isInstance = cable.Owner.Type instanceof port.Type + // } else { + // isInstance = port.Type instanceof cable.Owner.Type + // } + // } + // ========================================== + + // Remove cable if type restriction + // if !isInstance || (cable.Owner.Type == types.Function && port.Type != types.Function || cable.Owner.Type != types.Function && port.Type == types.Function) { + if cable.Owner.Type == types.Function && port.Type != types.Function || cable.Owner.Type != types.Function && port.Type == types.Function { + port._cableConnectError("cable.wrong_type_pair", &CableErrorEvent{ + Cable: cable, + Port: port, + Target: cable.Owner, + }, true) + + cable.Disconnect() + return false } -} -func (gs *PortOutputGetterSetter) Get() any { - port := gs.port + // Restrict connection between function input/output node with variable node + // Connection to similar node function IO or variable node also restricted + // These port is created on runtime dynamically + if port.Iface._dynamicPort && cable.Owner.Iface._dynamicPort { + port._cableConnectError("cable.unsupported_dynamic_port", &CableErrorEvent{ + Cable: cable, + Port: port, + Target: cable.Owner, + }, true) + + cable.Disconnect() + return false + } - if port.Feature == PortTypeArrayOf { - var tempVal any - if port.Value == nil { - tempVal = port.Default - } else { - tempVal = port.Value + // Remove cable if there are similar connection for the ports + for _, cable := range cable.Owner.Cables { + if utils.Contains(port.Cables, cable) { + port._cableConnectError("cable.duplicate_removed", &CableErrorEvent{ + Cable: cable, + Port: port, + Target: cable.Owner, + }, false) + + cable.Disconnect() + return false } - - return [](any){tempVal} } - if port.Value == nil { - return port.Default + // Put port reference to the cable + cable.Target = port + + var inp *Port + var out *Port + if cable.Target.Source == PortInput { + /** @var Port */ + inp = cable.Target + out = cable.Owner + } else { + /** @var Port */ + inp = cable.Owner + out = cable.Target } - return port.Value -} + // Remove old cable if the port not support array + if inp.Feature != PortTypeArrayOf && inp.Type != types.Function { + cables := inp.Cables // Cables in input port -func (port *Port) CreateLinker() GetterSetter { - if port.Source == PortInput { - return &PortInputGetterSetter{port: port} - } + if len(cables) != 0 { + temp := cables[0] - return &PortOutputGetterSetter{port: port} -} + if temp == cable { + temp = cables[1] + } -func (port *Port) sync() { - var target *Port - for _, cable := range port.Cables { - if cable.Owner == port { - target = cable.Target - } else { - target = cable.Owner - } + if temp != nil { + inp._cableConnectError("cable.replaced", &CableErrorEvent{ + Cable: cable, + OldCable: temp, + Port: inp, + Target: out, + }, false) - if target.Iface.QRequesting == false { - utils.CallFunction(target.Iface.Node, "Update", &[]reflect.Value{ - reflect.ValueOf(cable), - }) + temp.Disconnect() + return false + } } + } - target.QTrigger("value", port) + // Connect this cable into port's cable list + port.Cables = append(port.Cables, cable) + // cable.Connecting() + cable._connected() + + return true +} +func (port *Port) ConnectPort(portTarget *Port) bool { + cable := newCable(portTarget, port) + if portTarget._ghost { + cable._ghost = true } + + portTarget.Cables = append(portTarget.Cables, cable) + return port.ConnectCable(cable) } diff --git a/engine/portFeature.go b/engine/portFeature.go new file mode 100644 index 0000000..caaccfb --- /dev/null +++ b/engine/portFeature.go @@ -0,0 +1,92 @@ +package engine + +import ( + "github.com/blackprint/engine-go/types" + "github.com/blackprint/engine-go/utils" +) + +func portArrayOf_validate(source *Port, target *Port) bool { + if source.Type == target.Type || source.Type == types.Any || target.Type == types.Any { + return true + } + + if source.Types != nil && utils.Contains(source.Types, target.Type) { + return true + } + + return false +} + +func portUnion_validate(source *Port, target *Port) bool { + if source.Types != nil && target.Types != nil { + if len(source.Types) != len(target.Types) { + return false + } + + for _, type_ := range source.Types { + if !utils.Contains(target.Types, type_) { + return false + } + } + + return true + } + + return target.Type == types.Any || utils.Contains(source.Types, target.Type) +} + +func portStructOf_split(port *Port) { + if port.Source == PortInput { + panic("Port with feature 'StructOf' only supported for output port") + } + + node := port.Iface.Node + struct_ := &port.Struct + + for key, val := range *struct_ { + name := port.Name + key + newPort := node.CreatePort("output", name, val.Type) + newPort._parent = port + newPort._structSplitted = true + } + + port.Splitted = true + port.DisconnectAll() + + portData := node.Output[port.Name] + if portData != nil { + portStructOf_handle(port, portData) + } +} + +func portStructOf_unsplit(port *Port) { + parent := port._parent + if parent == nil && port.Struct != nil { + parent = port + } + + parent.Splitted = false + node := port.Iface.Node + + for key := range parent.Struct { + node.DeletePort("output", parent.Name+key) + } +} + +func portStructOf_handle(port *Port, data any) { + output := port.Iface.Node.Output + + if data != nil { + for key, val := range port.Struct { + if val.Field == "" { + output[key].Set(utils.GetProperty(data, val.Field)) + } else { + output[key].Set(val.Handle(data)) + } + } + } else { + for key := range port.Struct { + output[key].Set(nil) + } + } +} diff --git a/engine/portGetterSetter.go b/engine/portGetterSetter.go new file mode 100644 index 0000000..c5ab4f5 --- /dev/null +++ b/engine/portGetterSetter.go @@ -0,0 +1,179 @@ +package engine + +import ( + "github.com/blackprint/engine-go/types" +) + +type portInputGetterSetter struct { + getterSetter + port *Port +} + +func (gs *portInputGetterSetter) Set(val any) { + panic("Can't set input port's value") +} + +func (gs *portInputGetterSetter) Call() { + gs.port._func(gs.port) + gs.port.Iface.Node.Routes.RouteOut() +} + +func (gs *portInputGetterSetter) Get() any { + port := gs.port + + // This port must use values from connected output + cableLen := len(port.Cables) + + if cableLen == 0 { + if port.Feature == PortTypeArrayOf { + // ToDo: fix type to follow + // the type from port.Type + + return [](any){} + } + + return port.Default + } + + // Flag current iface is requesting value to other iface + + // Return single data + if cableLen == 1 { + temp := port.Cables[0] + var target *Port + + if temp.Owner == port { + target = temp.Target + } else { + target = temp.Owner + } + + if target.Value == nil { + port.Iface._requesting = true + target.Iface.Node.Request(temp) + port.Iface._requesting = false + } + + // fmt.Printf("1. %s -> %s (%s)\n", port.Name, target.Name, target.Value) + + if port.Feature == PortTypeArrayOf { + var tempVal any + if target.Value == nil { + tempVal = target.Default + } else { + tempVal = target.Value + } + + return [](any){tempVal} + } + + if target.Value == nil { + return target.Default + } else { + return target.Value + } + } + + // Return multiple data as an array + data := []any{} + for _, cable := range port.Cables { + var target *Port + if cable.Owner == port { + target = cable.Target + } else { + target = cable.Owner + } + + if target.Value == nil { + port.Iface._requesting = true + target.Iface.Node.Request(cable) + port.Iface._requesting = false + } + + // fmt.Printf("2. %s -> %s (%s)\n", port.Name, target.Name, target.Value) + + if target.Value == nil { + data = append(data, target.Default) + } else { + data = append(data, target.Value) + } + } + + if port.Feature != PortTypeArrayOf { + return data[0] + } + + return data +} + +type portOutputGetterSetter struct { + getterSetter + port *Port +} + +func (gs *portOutputGetterSetter) Set(val any) { + port := gs.port + + if port.Source == PortInput { + panic("Can't set data to input port") + } + + // ToDo: do we need feature validation here? + // fmt.Printf("3. %s = %s\n", port.Name, val) + + port.Value = val + port.Emit("value", &PortValueEvent{ + Port: port, + }) + port.sync() +} + +// createCallablePort +// createCallableRoutePort +func (gs *portOutputGetterSetter) Call() { + if gs.port.Type == types.Route { + cable := gs.port.Cables[0] + if cable == nil { + return + } + + cable.Input.RoutePort.RouteIn(cable) + } else { + for _, cable := range gs.port.Cables { + target := cable.Input + if target == nil { + continue + } + + // fmt.Println(cable.String()) + if target.Name_ != nil { + target.Iface._parentFunc.Node.Output[target.Name_.Name].Call() + } else { + target.Iface.Node.Input[target.Name].Call() + } + } + + gs.port.Emit("call", nil) + } +} + +func (gs *portOutputGetterSetter) Get() any { + port := gs.port + + if port.Feature == PortTypeArrayOf { + var tempVal any + if port.Value == nil { + tempVal = port.Default + } else { + tempVal = port.Value + } + + return [](any){tempVal} + } + + if port.Value == nil { + return port.Default + } + + return port.Value +} diff --git a/engine/portGhost.go b/engine/portGhost.go new file mode 100644 index 0000000..50f0f22 --- /dev/null +++ b/engine/portGhost.go @@ -0,0 +1,19 @@ +package engine + +var fakeIface = &Interface{ + Title: "Blackprint.PortGhost", + IsGhost: true, + Node: &Node{ + Instance: &Instance{}, + }, +} + +func OutputPort(type_ any) *Port { + port := fakeIface._createPort("Output", "Blackprint.OutputPort", type_) + return port +} + +func InputPort(type_ any) *Port { + port := fakeIface._createPort("Input", "Blackprint.InputPort", type_) + return port +} diff --git a/engine/portTypes.go b/engine/portTypes.go new file mode 100644 index 0000000..3f23f77 --- /dev/null +++ b/engine/portTypes.go @@ -0,0 +1,74 @@ +package engine + +import ( + "reflect" +) + +type portObject struct{} + +var QPorts *portObject + +func init() { + QPorts = &portObject{} +} + +/* This port can contain multiple cable as input + * and the value will be array of 'type' + * it's only one type, not union + * for union port, please split it to different port to handle it + */ +func (*portObject) ArrayOf(type_ reflect.Kind) *portFeature { + return &portFeature{ + Id: PortTypeArrayOf, + Type: type_, + } +} + +/* This port can have default value if no cable was connected + * type = Type Data that allowed for the Port + * value = default value for the port + */ +func (*portObject) Default(type_ reflect.Kind, val any) *portFeature { + return &portFeature{ + Id: PortTypeDefault, + Type: type_, + Value: val, + } +} + +/* This port will be used as a trigger or callable input port + * func (*portObject) = callback when the port was being called as a function + */ +func (*portObject) Trigger(callback func(*Port)) *portFeature { + return &portFeature{ + Id: PortTypeTrigger, + Func: callback, + } +} + +/* This port can allow multiple different types + * like an 'any' port, but can only contain one value + */ +func (*portObject) Union(types []reflect.Kind) *portFeature { + return &portFeature{ + Id: PortTypeUnion, + Types: types, + } +} + +/* This port can allow multiple different types + * like an 'any' port, but can only contain one value + */ +func (*portObject) StructOf(type_ reflect.Kind, structure map[string]PortStructTemplate) *portFeature { + return &portFeature{ + Id: PortTypeStructOf, + Type: type_, + Value: structure, + } +} + +func (*portObject) Route() *portFeature { + return &portFeature{ + Id: PortTypeRoute, + } +} diff --git a/engine/references.go b/engine/references.go new file mode 100644 index 0000000..d9b9166 --- /dev/null +++ b/engine/references.go @@ -0,0 +1,8 @@ +package engine + +type referencesShortcut struct { + IInput map[string]*Port + Input map[string]*portInputGetterSetter + IOutput map[string]*Port + Output map[string]*portOutputGetterSetter +} diff --git a/engine/routePort.go b/engine/routePort.go new file mode 100644 index 0000000..acc01f2 --- /dev/null +++ b/engine/routePort.go @@ -0,0 +1,110 @@ +package engine + +import ( + "github.com/blackprint/engine-go/engine/nodes" + "github.com/blackprint/engine-go/utils" +) + +type routePort struct { + *Port + In []*Cable + Out *Cable + DisableOut bool + Disabled bool + IsRoute bool + Iface *Interface + + // for internal library use only + _isPaused bool +} + +func newRoutePort(iface *Interface) *routePort { + temp := &routePort{ + Iface: iface, + IsRoute: true, + } + temp.Port = &Port{RoutePort: temp, Iface: iface} + temp.Port.Cables = []*Cable{} + temp.In = temp.Port.Cables + return temp +} + +// Connect other route port (this .out to other .in port) +func (r *routePort) RouteTo(iface *Interface) { + if r.Out != nil { + r.Out.Disconnect() + } + + if iface == nil { + cable := newCable(r.Port, nil) + cable.IsRoute = true + r.Out = cable + return + } + + port := iface.Node.Routes + + cable := newCable(r.Port, port.Port) + cable.IsRoute = true + cable.Output = r.Port + r.Out = cable + port.In = append(port.In, cable) // ToDo: check if this empty if the connected cable was disconnected + + cable._connected() +} + +func (r *routePort) ConnectCable(cable *Cable) bool { + if utils.Contains(r.In, cable) { + return false + } + + r.In = append(r.In, cable) + cable.Input = r.Port + cable.Target = r.Port + cable._connected() + + return true +} + +func (r *routePort) RouteIn(cable *Cable) { + node := r.Iface.Node + node.Update(cable) + + routes := node.Routes + routes.RouteOut() +} + +func (r *routePort) RouteOut() { + if r.DisableOut { + return + } + + if r.Out == nil { + if r.Iface._enum == nodes.BPFnOutput { + node := r.Iface._funcMain.Node + route := node.Routes + route.RouteIn(nil) + } + + return + } + + targetRoute := r.Out.Input.RoutePort + if targetRoute == nil { + return + } + + enum := targetRoute.Iface._enum + + if enum == 0 { + targetRoute.RouteIn(r.Out) + } else if enum == nodes.BPFnMain { + routes := targetRoute.Iface._proxyInput.Routes + routes.RouteIn(r.Out) + } else if enum == nodes.BPFnOutput { + targetRoute.Iface.Node.Update(nil) + targetRoute.Iface._funcMain.Node.Routes.RouteOut() + } else { + targetRoute.RouteIn(r.Out) + } +} diff --git a/example/button.go b/example/button.go index 0831451..a4f49c1 100644 --- a/example/button.go +++ b/example/button.go @@ -5,49 +5,39 @@ import ( Blackprint "github.com/blackprint/engine-go/blackprint" "github.com/blackprint/engine-go/engine" - "github.com/blackprint/engine-go/types" ) // ============ type ButtonSimple struct { - *engine.Node + *engine.EmbedNode } type ButtonSimpleIFace struct { - *engine.Interface + *engine.EmbedInterface } func (iface *ButtonSimpleIFace) Clicked(ev any) { log.Printf("\x1b[1m\x1b[33mButton\\Simple:\x1b[0m \x1b[33mI got '%d', time to trigger to the other node\x1b[0m\n", ev) - iface.Node.(*ButtonSimple).Output["Clicked"].Set(ev) + iface.Node.Output["Clicked"].Set(ev) } // This will be called from example.go -func RegisterButton() { - ButtonSimpleOutput := engine.NodePort{ - "Clicked": types.Function, - } +func init() { + Blackprint.RegisterNode("Example/Button/Simple", &engine.NodeRegister{ + Output: engine.PortTemplate{}, + Input: engine.PortTemplate{}, - Blackprint.RegisterNode("Example/Button/Simple", func(instance *engine.Instance) any { - node := &ButtonSimple{ - Node: &engine.Node{ - Instance: instance, + Constructor: func(node *engine.Node) { + node.Embed = &ButtonSimple{} - // Node's Output Port Template - TOutput: ButtonSimpleOutput, - }, - } - - iface := node.SetInterface("BPIC/Example/Button").(*ButtonSimpleIFace) - iface.Title = "Button" - - return node + iface := node.SetInterface("BPIC/Example/Button") + iface.Title = "Button" + }, }) - Blackprint.RegisterInterface("BPIC/Example/Button", func(node any) any { - // node_ := node.(ButtonSimple) - return &ButtonSimpleIFace{ - Interface: &engine.Interface{}, - } + Blackprint.RegisterInterface("BPIC/Example/Button", &engine.InterfaceRegister{ + Constructor: func(iface *engine.Interface) { + iface.Embed = &ButtonSimpleIFace{} + }, }) } diff --git a/example/example.go b/example/example.go deleted file mode 100644 index 082c7e4..0000000 --- a/example/example.go +++ /dev/null @@ -1,8 +0,0 @@ -package example - -func RegisterAll() { - RegisterButton() - RegisterDisplay() - RegisterInput() - RegisterMath() -} \ No newline at end of file diff --git a/example/example_test.go b/example/example_test.go index 7a2e7fd..c5a04c5 100644 --- a/example/example_test.go +++ b/example/example_test.go @@ -11,36 +11,37 @@ import ( // for benchmarking var instance *engine.Instance -var input *InputSimpleIFace +var input *engine.Interface func TestMain(m *testing.M) { log.SetFlags(0) // log.SetOutput(ioutil.Discard) - RegisterAll() + // Nodes already be registered from ./example.go -> init() // === Import JSON after all nodes was registered === // You can import the JSON to Blackprint Sketch if you want to view the nodes visually instance = engine.New() json := `{"Example/Math/Random":[{"i":0,"x":298,"y":73,"output":{"Out":[{"i":2,"name":"A"}]}},{"i":1,"x":298,"y":239,"output":{"Out":[{"i":2,"name":"B"}]}}],"Example/Math/Multiply":[{"i":2,"x":525,"y":155,"output":{"Result":[{"i":3,"name":"Any"}]}}],"Example/Display/Logger":[{"i":3,"id":"myLogger","x":763,"y":169}],"Example/Button/Simple":[{"i":4,"id":"myButton","x":41,"y":59,"output":{"Clicked":[{"i":2,"name":"Exec"}]}}],"Example/Input/Simple":[{"i":5,"id":"myInput","x":38,"y":281,"data":{"value":"saved input"},"output":{"Changed":[{"i":1,"name":"Re-seed"}],"Value":[{"i":3,"name":"Any"}]}}]}` + // json := `{"_":{"moduleJS":["http://localhost:6789/dist/nodes-example.mjs"],"functions":{"Test":{"id":"Test","title":"Test","description":"No description","vars":["shared"],"privateVars":["private"],"structure":{"BP/Fn/Input":[{"i":0,"x":389,"y":100,"z":3,"output":{"A":[{"i":2,"name":"A"}],"Exec":[{"i":2,"name":"Exec"}]}}],"BP/Fn/Output":[{"i":1,"x":973,"y":228,"z":14}],"Example/Math/Multiply":[{"i":2,"x":656,"y":99,"z":8,"output":{"Result":[{"i":3,"name":"Val"},{"i":9,"name":"Val"}]}},{"i":10,"x":661,"y":289,"z":4,"output":{"Result":[{"i":5,"name":"Val"},{"i":1,"name":"Result1"}]}}],"BP/Var/Set":[{"i":3,"x":958,"y":142,"z":9,"data":{"name":"shared","scope":2}},{"i":5,"x":971,"y":333,"z":2,"data":{"name":"private","scope":1},"route":{"i":1}}],"BP/Var/Get":[{"i":4,"x":387,"y":461,"z":5,"data":{"name":"shared","scope":2},"output":{"Val":[{"i":8,"name":"Any"}]}},{"i":6,"x":389,"y":524,"z":0,"data":{"name":"private","scope":1},"output":{"Val":[{"i":8,"name":"Any"}]}}],"BP/FnVar/Input":[{"i":7,"x":387,"y":218,"z":7,"data":{"name":"B"},"output":{"Val":[{"i":2,"name":"B"}]}},{"i":11,"x":386,"y":301,"z":6,"data":{"name":"Exec"},"output":{"Val":[{"i":10,"name":"Exec"}]}},{"i":12,"x":386,"y":370,"z":10,"data":{"name":"A"},"output":{"Val":[{"i":10,"name":"A"},{"i":10,"name":"B"}]}}],"Example/Display/Logger":[{"i":8,"x":661,"y":474,"z":11}],"BP/FnVar/Output":[{"i":9,"x":956,"y":69,"z":1,"data":{"name":"Result"}},{"i":14,"x":969,"y":629,"z":13,"data":{"name":"Clicked"}}],"Example/Button/Simple":[{"i":13,"x":634,"y":616,"z":12,"output":{"Clicked":[{"i":14,"name":"Val"}]}}]}}}},"Example/Math/Random":[{"i":0,"x":512,"y":76,"z":0,"output":{"Out":[{"i":5,"name":"A"}]},"route":{"i":5}},{"i":1,"x":512,"y":242,"z":1,"output":{"Out":[{"i":5,"name":"B"}]}}],"Example/Display/Logger":[{"i":2,"x":986,"y":282,"z":2,"id":"myLogger"}],"Example/Button/Simple":[{"i":3,"x":244,"y":64,"z":6,"id":"myButton","output":{"Clicked":[{"i":5,"name":"Exec"}]}}],"Example/Input/Simple":[{"i":4,"x":238,"y":279,"z":4,"id":"myInput","data":{"value":"saved input"},"output":{"Changed":[{"i":1,"name":"Re-seed"}],"Value":[{"i":2,"name":"Any"}]}}],"BPI/F/Test":[{"i":5,"x":738,"y":138,"z":5,"output":{"Result1":[{"i":2,"name":"Any"}],"Result":[{"i":2,"name":"Any"}],"Clicked":[{"i":6,"name":"Exec"}]},"route":{"i":6}}],"Example/Math/Multiply":[{"i":6,"x":1032,"y":143,"z":3}]}` instance.ImportJSON([]byte(json)) // Because Golang lack of getter and setter, We need to get or set like calling a function // Anyway.. lets to run something :) - button := instance.IFace["myButton"].(*ButtonSimpleIFace) + button := instance.Iface["myButton"].Embed.(*ButtonSimpleIFace) log.Println("\n>> I'm clicking the button") button.Clicked(123) - logger := instance.IFace["myLogger"].(*LoggerIFace) + logger := instance.Iface["myLogger"].Embed.(*LoggerIFace) log.Println("\n>> I got the output value: " + logger.Log().(string)) log.Println("\n>> I'm writing something to the input box") - input = instance.IFace["myInput"].(*InputSimpleIFace) + input = instance.Iface["myInput"] input.Data["value"].Set("hello wrold") // you can also use getNodes if you haven't set the ID - myLogger := instance.GetNodes("Example/Display/Logger")[0].(*LoggerNode).IFace.(*LoggerIFace) + myLogger := instance.GetNodes("Example/Display/Logger")[0].Embed.(*LoggerIFace) log.Println("\n>> I got the output value: " + myLogger.Log().(string)) } diff --git a/example/input.go b/example/input.go index fb9b6b2..4273d98 100644 --- a/example/input.go +++ b/example/input.go @@ -10,32 +10,32 @@ import ( // ============ type InputSimple struct { - *engine.Node + *engine.EmbedNode } // Bring value from imported iface to node output -func (node *InputSimple) Imported() { - val := node.IFace.(*InputSimpleIFace).Data["value"].Get() +func (this *InputSimple) Imported(map[string]any) { + val := this.Iface.Data["value"].Get() if val != nil { log.Printf("\x1b[1m\x1b[33mInput\\Simple:\x1b[0m \x1b[33mSaved data as output: %s\x1b[0m\n", val) } - node.Output["Value"].Set(val) + this.Node.Output["Value"].Set(val) } type InputSimpleIFace struct { - *engine.Interface + *engine.EmbedInterface } -func (iface *InputSimpleIFace) Changed(val any) { +func (this *InputSimpleIFace) Changed(val any) { // This node still being imported - if iface.Importing != false { + if this.Iface.Importing != false { return } log.Printf("\x1b[1m\x1b[33mInput\\Simple:\x1b[0m \x1b[33mThe input box have new value: %s\x1b[0m\n", val) - node := iface.Node.(*InputSimple) + node := this.Node node.Output["Value"].Set(val) // This will call every connected node @@ -43,52 +43,41 @@ func (iface *InputSimpleIFace) Changed(val any) { } type MyData struct { - IFace any - val any + engine.GetterSetter + Value any } func (gs *MyData) Set(val any) { - gs.val = val - gs.IFace.(*InputSimpleIFace).Changed(gs.val) + gs.Value = val + gs.Iface.Embed.(*InputSimpleIFace).Changed(gs.Value) } func (gs *MyData) Get() any { - return gs.val + return gs.Value } // This will be called from example.go -func RegisterInput() { - Blackprint.RegisterNode("Example/Input/Simple", func(instance *engine.Instance) any { - node := &InputSimple{ - Node: &engine.Node{ - Instance: instance, - - // Node's Output Port Template - TOutput: engine.NodePort{ - "Changed": types.Function, - "Value": types.String, - }, - }, - } - - iface := node.SetInterface("BPIC/Example/Input").(*InputSimpleIFace) - iface.Title = "Input" - - return node +func init() { + Blackprint.RegisterNode("Example/Input/Simple", &engine.NodeRegister{ + Output: engine.PortTemplate{ + "Changed": types.Function, + "Value": types.String, + }, + + Constructor: func(node *engine.Node) { + node.Embed = &InputSimple{} + + iface := node.SetInterface("BPIC/Example/Input") + iface.Title = "Input" + }, }) - Blackprint.RegisterInterface("BPIC/Example/Input", func(node_ any) any { - // node := node_.(InputSimple) - - var iface *InputSimpleIFace - iface = &InputSimpleIFace{ - Interface: &engine.Interface{ - Data: engine.InterfaceData{ - "value": &MyData{val: "..."}, - }, - }, - } - - return iface + Blackprint.RegisterInterface("BPIC/Example/Input", &engine.InterfaceRegister{ + Constructor: func(iface *engine.Interface) { + iface.Embed = &InputSimpleIFace{} + iface.Data = engine.InterfaceData{ + "value": &MyData{Value: "..."}, + } + }, }) } diff --git a/example/logger.go b/example/logger.go index 2a9be62..3f489a3 100644 --- a/example/logger.go +++ b/example/logger.go @@ -7,49 +7,44 @@ import ( Blackprint "github.com/blackprint/engine-go/blackprint" "github.com/blackprint/engine-go/engine" - "github.com/blackprint/engine-go/port" + "github.com/blackprint/engine-go/types" ) -// This will be called from example.go -func RegisterDisplay() { - RegisterLogger() -} - // ============ type LoggerNode struct { - *engine.Node + *engine.EmbedNode } type LoggerIFace struct { - *engine.Interface + *engine.EmbedInterface log string } -func (iface *LoggerIFace) Init() { +func (this *LoggerIFace) Init() { refreshLogger := func(val any) { if val == nil { val = "nil" - iface.Log(val) + this.Log(val) } else { types := reflect.TypeOf(val).Kind() if types == reflect.String || types == reflect.Int64 || types == reflect.Float64 { - iface.Log(val) + this.Log(val) } else { byte_, _ := json.Marshal(val) - iface.Log(string(byte_)) // ToDo, convert any object to JSON string + this.Log(string(byte_)) // ToDo, convert any object to JSON string } } } - node := iface.Node.(*LoggerNode) + node := this.Node // Let's show data after new cable was connected or disconnected - iface.On("cable.connect cable.disconnect", func(_cable any) { + this.Iface.On("cable.connect cable.disconnect", func(_cable any) { log.Printf("\x1b[1m\x1b[33mDisplay\\Logger:\x1b[0m \x1b[33mA cable was changed on Logger, now refresing the input element\x1b[0m\n") refreshLogger(node.Input["Any"].Get()) }) - iface.Input["Any"].On("value", func(_port any) { + this.Iface.Input["Any"].On("value", func(_port any) { port := _port.(*engine.Port) log.Printf("\x1b[1m\x1b[33mDisplay\\Logger:\x1b[0m \x1b[33mI connected to %s (port %s), that have new value: %v\x1b[0m\n", port.Iface.Title, port.Name, port.Value) @@ -69,29 +64,23 @@ func (iface *LoggerIFace) Log(val ...any) any { return nil } -func RegisterLogger() { - Blackprint.RegisterNode("Example/Display/Logger", func(instance *engine.Instance) any { - node := &LoggerNode{ - Node: &engine.Node{ - Instance: instance, +func init() { + Blackprint.RegisterNode("Example/Display/Logger", &engine.NodeRegister{ + Input: engine.PortTemplate{ + "Any": Blackprint.Port.ArrayOf(types.Any), // nil => Any + }, - // Node's Input Port Template - TInput: engine.NodePort{ - "Any": port.ArrayOf(reflect.Interface), // nil => Any - }, - }, - } + Constructor: func(node *engine.Node) { + node.Embed = &LoggerNode{} - iface := node.SetInterface("BPIC/Example/Display/Logger").(*LoggerIFace) - iface.Title = "Logger" - - return node + iface := node.SetInterface("BPIC/Example/Display/Logger") + iface.Title = "Logger" + }, }) - Blackprint.RegisterInterface("BPIC/Example/Display/Logger", func(node_ any) any { - // node := node_.(LoggerNode) - return &LoggerIFace{ - Interface: &engine.Interface{}, - } + Blackprint.RegisterInterface("BPIC/Example/Display/Logger", &engine.InterfaceRegister{ + Constructor: func(iface *engine.Interface) { + iface.Embed = &LoggerIFace{} + }, }) } diff --git a/example/math.go b/example/math.go index 5e3c550..97894f3 100644 --- a/example/math.go +++ b/example/math.go @@ -4,135 +4,101 @@ import ( "crypto/rand" "encoding/binary" "log" - "reflect" - "strconv" Blackprint "github.com/blackprint/engine-go/blackprint" "github.com/blackprint/engine-go/engine" - "github.com/blackprint/engine-go/port" "github.com/blackprint/engine-go/types" ) -// This will be called from example.go -func RegisterMath() { - RegisterMathMultiply() - RegisterMathRandom() -} - -// ============ +// ============ MathMultiple Node ============ type MathMultiple struct { - engine.Node + engine.EmbedNode } // Your own processing mechanism -func (node *MathMultiple) Multiply() int { - log.Printf("\x1b[1m\x1b[33mMath\\Multiply:\x1b[0m \x1b[33mMultiplying %d with %d\x1b[0m\n", node.Input["A"].Get().(int), node.Input["B"].Get().(int)) - return node.Input["A"].Get().(int) * node.Input["B"].Get().(int) +func (this *MathMultiple) Multiply() int { + log.Printf("\x1b[1m\x1b[33mMath\\Multiply:\x1b[0m \x1b[33mMultiplying %d with %d\x1b[0m\n", this.Node.Input["A"].Get().(int), this.Node.Input["B"].Get().(int)) + return this.Node.Input["A"].Get().(int) * this.Node.Input["B"].Get().(int) } // When any output value from other node are updated // Let's immediately change current node result -func (node *MathMultiple) Update(cable *engine.Cable) { - node.Output["Result"].Set(node.Multiply()) +func (this *MathMultiple) Update(cable *engine.Cable) { + this.Node.Output["Result"].Set(this.Multiply()) } -func RegisterMathMultiply() { - Blackprint.RegisterNode("Example/Math/Multiply", func(instance *engine.Instance) any { - var node MathMultiple - node = MathMultiple{ - Node: engine.Node{ - Instance: instance, - - // Node's Input Port Template - TInput: engine.NodePort{ - "Exec": port.Trigger(func(port *engine.Port) { - node.Output["Result"].Set(node.Multiply()) - log.Printf("\x1b[1m\x1b[33mMath\\Multiply:\x1b[0m \x1b[33mResult has been set: %d\x1b[0m\n", node.Output["Result"].Get()) - }), - "A": types.Int, - "B": port.Validator(types.Int, func(val any) any { - log.Printf("\x1b[1m\x1b[33mMath\\Multiply:\x1b[0m \x1b[33m%s - Port B got input: %d\x1b[0m\n", node.IFace.(*engine.Interface).Title, val) - - // Convert string to number - if reflect.ValueOf(val).Kind() == reflect.String { - num, _ := strconv.Atoi(val.(string)) - return num - } - - return val - }), - }, - - // Node's Output Port Template - TOutput: engine.NodePort{ - "Result": types.Int, - }, - }, - } - - iface := node.SetInterface().(*engine.Interface) // default interface - iface.Title = "Multiply" - - node.On("cable.connect", func(event any) { - ev := event.(engine.CableEvent) - log.Printf("\x1b[1m\x1b[33mMath\\Multiply:\x1b[0m \x1b[33mCable connected from %s (%s) to %s (%s)\x1b[0m\n", ev.Port.Iface.Title, ev.Port.Name, ev.Target.Iface.Title, ev.Target.Name) - }) - - return &node +func init() { + Blackprint.RegisterNode("Example/Math/Multiply", &engine.NodeRegister{ + Input: engine.PortTemplate{ + "Exec": Blackprint.Port.Trigger(func(port *engine.Port) { + port.Iface.Node.Output["Result"].Set(port.Iface.Node.Embed.(*MathMultiple).Multiply()) + log.Printf("\x1b[1m\x1b[33mMath\\Multiply:\x1b[0m \x1b[33mResult has been set: %d\x1b[0m\n", port.Iface.Node.Output["Result"].Get()) + }), + "A": types.Int, + "B": types.Any, + }, + + Output: engine.PortTemplate{ + "Result": types.Int, + }, + + Constructor: func(node *engine.Node) { + node.Embed = &MathMultiple{} + + iface := node.SetInterface() + iface.Title = "Multiply" + + iface.On("cable.connect", func(event any) { + ev := event.(engine.CableEvent) + log.Printf("\x1b[1m\x1b[33mMath\\Multiply:\x1b[0m \x1b[33mCable connected from %s (%s) to %s (%s)\x1b[0m\n", ev.Port.Iface.Title, ev.Port.Name, ev.Target.Iface.Title, ev.Target.Name) + }) + }, }) } -// ============ +// ============ MathRandom Node ============ type MathRandom struct { - engine.Node + engine.EmbedNode Executed bool } // When the connected node is requesting for the output value -func (node *MathRandom) Request(port *engine.Port, iface_ any) bool { +func (this *MathRandom) Request(cable *engine.Cable) { // Only run once this node never been executed // Return false if no value was changed - if node.Executed == true { - return false + if this.Executed == true { + return } - iface := iface_.(*engine.Interface) - log.Printf("\x1b[1m\x1b[33mMath\\Random:\x1b[0m \x1b[33mValue request for port: %s, from node: %s\x1b[0m\n", port.Name, iface.Title) + log.Printf("\x1b[1m\x1b[33mMath\\Random:\x1b[0m \x1b[33mValue request for port: %s, from node: %s\x1b[0m\n", cable.Output.Name, cable.Input.Iface.Title) // Let's create the value for him - node.Input["Re-seed"].Call() + this.Node.Input["Re-seed"].Call() - return true + return } -func RegisterMathRandom() { - Blackprint.RegisterNode("Example/Math/Random", func(instance *engine.Instance) any { - var node MathRandom - node = MathRandom{ - Executed: false, - Node: engine.Node{ - Instance: instance, - - // Node's Input Port Template - TInput: engine.NodePort{ - "Re-seed": port.Trigger(func(port *engine.Port) { - node.Executed = true - byt := make([]byte, 2) - rand.Read(byt) - node.Output["Out"].Set(int(binary.BigEndian.Uint16(byt[:])) % 100) - }), - }, - - // Node's Output Port Template - TOutput: engine.NodePort{ - "Out": types.Int, - }, - }, - } - - iface := node.SetInterface().(*engine.Interface) // default interface - iface.Title = "Random" - - return &node +func init() { + Blackprint.RegisterNode("Example/Math/Random", &engine.NodeRegister{ + Input: engine.PortTemplate{ + "Re-seed": Blackprint.Port.Trigger(func(port *engine.Port) { + node := port.Iface.Node + node.Embed.(*MathRandom).Executed = true + + byt := make([]byte, 2) + rand.Read(byt) + node.Output["Out"].Set(int(binary.BigEndian.Uint16(byt[:])) % 100) + }), + }, + Output: engine.PortTemplate{ + "Out": types.Int, + }, + + Constructor: func(node *engine.Node) { + node.Embed = &MathRandom{} + + iface := node.SetInterface() + iface.Title = "Random" + }, }) } diff --git a/go.mod b/go.mod index cf28165..7a31b5e 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/blackprint/engine-go go 1.19 require ( - github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/spf13/cobra v1.2.1 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/spf13/cobra v1.5.0 // indirect github.com/spf13/pflag v1.0.5 // indirect ) diff --git a/go.sum b/go.sum index 9290f91..eb38d7c 100644 --- a/go.sum +++ b/go.sum @@ -56,6 +56,7 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -160,6 +161,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= @@ -195,6 +198,7 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -204,6 +208,8 @@ github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= diff --git a/internal/cmd/root.go b/internal/cmd/root.go index 84cb56e..f34d8a7 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -6,6 +6,6 @@ var ( RootCmd = &cobra.Command{ Use: "engine-go", Short: "engine-go for Blackprint", - Long: "engine-go is Blackprint engine", + Long: "engine-go is Blackprint Engine for Golang", } ) diff --git a/port/types.go b/port/types.go deleted file mode 100644 index d5b300b..0000000 --- a/port/types.go +++ /dev/null @@ -1,73 +0,0 @@ -package port - -import ( - "reflect" - - engine "github.com/blackprint/engine-go/engine" -) - -/* This port can contain multiple cable as input - * and the value will be array of 'type' - * it's only one type, not union - * for union port, please split it to different port to handle it - */ -func ArrayOf(type_ reflect.Kind) *engine.PortFeature { - return &engine.PortFeature{ - Id: engine.PortTypeArrayOf, - Type: type_, - } -} - -/* This port can have default value if no cable was connected - * type = Type Data that allowed for the Port - * value = default value for the port - */ -func Default(type_ reflect.Kind, val any) *engine.PortFeature { - return &engine.PortFeature{ - Id: engine.PortTypeDefault, - Type: type_, - Value: val, - } -} - -/* Allow many cable connected to a port - * But only the last value that will used as value - */ -func Switch(type_ reflect.Kind) *engine.PortFeature { - return &engine.PortFeature{ - Id: engine.PortTypeSwitch, - Type: type_, - } -} - -/* This port will be used as a trigger or callable input port - * func = callback when the port was being called as a function - */ -func Trigger(callback func(*engine.Port)) *engine.PortFeature { - return &engine.PortFeature{ - Id: engine.PortTypeTrigger, - Func: callback, - } -} - -/* This port can allow multiple different types - * like an 'any' port, but can only contain one value - */ -func Union(types []reflect.Kind) *engine.PortFeature { - return &engine.PortFeature{ - Id: engine.PortTypeUnion, - Types: types, - } -} - -/* This port will allow any value to be passed to a function - * then you can write custom data validation in the function - * the value returned by your function will be used as the input value - */ -func Validator(type_ reflect.Kind, callback func(any) any) *engine.PortFeature { - return &engine.PortFeature{ - Id: engine.PortTypeValidator, - Type: type_, - Func: callback, - } -} diff --git a/types/types.go b/types/types.go index e637507..5da1d95 100644 --- a/types/types.go +++ b/types/types.go @@ -12,4 +12,5 @@ const ( Int = reflect.Int64 Float = reflect.Float64 Any = reflect.Interface + Route = 99999 // ToDo: do we need to fix this magic number? ) diff --git a/utils/test.go b/utils/test.go deleted file mode 100644 index e629a0e..0000000 --- a/utils/test.go +++ /dev/null @@ -1,35 +0,0 @@ -package utils - -import "fmt" - -type GetterSetter interface { - Set(val any) - Get() any -} - -type MyGetterSetter struct { - val any - aa []*map[string]GetterSetter -} - -func (gs *MyGetterSetter) Set(val any) { - gs.val = val -} - -func (gs *MyGetterSetter) Get() any { - return gs.aa == nil - // return gs.val -} - -func main() { - var aa map[string]GetterSetter - bb := []*map[string]GetterSetter{nil} - - aa = map[string]GetterSetter{ - "Result": &MyGetterSetter{val: 123, aa: bb}, - } - bb[0] = &aa - - aa["Result"].Set("Hello") - fmt.Println(aa["Result"].Get().(bool)) -} diff --git a/utils/utils.go b/utils/utils.go index b75890a..f9b0772 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -43,3 +43,74 @@ func CallFieldFunction(obj any, key string, val *[]reflect.Value) any { func HasProperty(obj any, key string) bool { return reflect.ValueOf(obj).Elem().FieldByName(key).IsValid() } + +// https://stackoverflow.com/a/71184501/6563200 +func IndexOf[T comparable](collection []T, el T) int { + for i, x := range collection { + if x == el { + return i + } + } + return -1 +} + +func IndexOfAny(collection []any, el any) int { + pointer := reflect.ValueOf(el).Pointer() + for i, x := range collection { + if reflect.ValueOf(x).Pointer() == pointer { + return i + } + } + return -1 +} + +func Contains[T comparable](collection []T, el T) bool { + for _, x := range collection { + if x == el { + return true + } + } + return false +} + +func ContainsAny(collection []any, el any) bool { + pointer := reflect.ValueOf(el).Pointer() + for _, x := range collection { + if reflect.ValueOf(x).Pointer() == pointer { + return true + } + } + return false +} + +func RemoveItem[T comparable](collection []T, el T) []T { + i := IndexOf(collection, el) + if i == -1 { + return collection + } + + return append(collection[:i], collection[i+1:]...) +} + +func RemoveItemAny(collection []any, el any) []any { + i := IndexOfAny(collection, el) + if i == -1 { + return collection + } + + return append(collection[:i], collection[i+1:]...) +} + +func RemoveItemAtIndex[T comparable](collection []T, i int) []T { + return append(collection[:i], collection[i+1:]...) +} + +func RemoveItemAtIndexAny(collection []any, i int) []any { + return append(collection[:i], collection[i+1:]...) +} + +func ClearMap[T any](collection map[string]T) { + for key := range collection { + delete(collection, key) + } +}