|  |  |  | @ -56,24 +56,46 @@ func (ext *ListenerExt) Process(ctx *Context, princ_id NodeID, node *Node, signa | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | // ReqState holds the multiple states of a requirement
 | 
		
	
		
			
				|  |  |  |  | type ReqState struct { | 
		
	
		
			
				|  |  |  |  | type LinkState struct { | 
		
	
		
			
				|  |  |  |  |   Link string `json:"link"` | 
		
	
		
			
				|  |  |  |  |   Lock string `json:"lock"` | 
		
	
		
			
				|  |  |  |  |   Initiator NodeID `json:"initiator"` | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | // A LockableExt allows a node to be linked to other nodes(via LinkSignal) and locked/unlocked(via LockSignal)
 | 
		
	
		
			
				|  |  |  |  | type LockableExt struct { | 
		
	
		
			
				|  |  |  |  |   Owner *NodeID | 
		
	
		
			
				|  |  |  |  |   PendingOwner *NodeID | 
		
	
		
			
				|  |  |  |  |   Requirements map[NodeID]ReqState | 
		
	
		
			
				|  |  |  |  |   Dependencies map[NodeID]string | 
		
	
		
			
				|  |  |  |  | type LinkMap map[NodeID]LinkState | 
		
	
		
			
				|  |  |  |  | func (m LinkMap) MarshalJSON() ([]byte, error) { | 
		
	
		
			
				|  |  |  |  |   tmp := map[string]LinkState{} | 
		
	
		
			
				|  |  |  |  |   for id, state := range(m) { | 
		
	
		
			
				|  |  |  |  |     tmp[id.String()] = state | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   return json.Marshal(tmp) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func (m LinkMap) UnmarshalJSON(data []byte) error { | 
		
	
		
			
				|  |  |  |  |   tmp := map[string]LinkState{} | 
		
	
		
			
				|  |  |  |  |   err := json.Unmarshal(data, &tmp) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   for id_str, state := range(tmp) { | 
		
	
		
			
				|  |  |  |  |     id, err := ParseID(id_str) | 
		
	
		
			
				|  |  |  |  |     if err != nil { | 
		
	
		
			
				|  |  |  |  |       return err | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     m[id] = state | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  |   return nil | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | type LockableExtJSON struct { | 
		
	
		
			
				|  |  |  |  | type LockableExt struct { | 
		
	
		
			
				|  |  |  |  |   Owner *NodeID `json:"owner"` | 
		
	
		
			
				|  |  |  |  |   PendingOwner *NodeID `json:"pending_owner"` | 
		
	
		
			
				|  |  |  |  |   Requirements map[string]ReqState `json:"requirements"` | 
		
	
		
			
				|  |  |  |  |   Dependencies map[string]string `json:"dependencies"` | 
		
	
		
			
				|  |  |  |  |   Requirements LinkMap `json:"requirements"` | 
		
	
		
			
				|  |  |  |  |   Dependencies LinkMap `json:"dependencies"` | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func (ext *LockableExt) Field(name string) interface{} { | 
		
	
	
		
			
				
					|  |  |  | @ -94,28 +116,13 @@ func (ext *LockableExt) Field(name string) interface{} { | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func LoadLockableExt(ctx *Context, data []byte) (Extension, error) { | 
		
	
		
			
				|  |  |  |  |   var j LockableExtJSON | 
		
	
		
			
				|  |  |  |  |   err := json.Unmarshal(data, &j) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return nil, err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   requirements, err := LoadIDMap(j.Requirements) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return nil, err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   dependencies, err := LoadIDMap(j.Dependencies) | 
		
	
		
			
				|  |  |  |  |   var ext LockableExt | 
		
	
		
			
				|  |  |  |  |   err := json.Unmarshal(data, &ext) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return nil, err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   return &LockableExt{ | 
		
	
		
			
				|  |  |  |  |     Owner: j.Owner, | 
		
	
		
			
				|  |  |  |  |     PendingOwner: j.PendingOwner, | 
		
	
		
			
				|  |  |  |  |     Requirements: requirements, | 
		
	
		
			
				|  |  |  |  |     Dependencies: dependencies, | 
		
	
		
			
				|  |  |  |  |   }, nil | 
		
	
		
			
				|  |  |  |  |   return &ext, nil | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func (ext *ListenerExt) Serialize() ([]byte, error) { | 
		
	
	
		
			
				
					|  |  |  | @ -127,20 +134,15 @@ func (ext *LockableExt) Type() ExtType { | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func (ext *LockableExt) Serialize() ([]byte, error) { | 
		
	
		
			
				|  |  |  |  |   return json.MarshalIndent(&LockableExtJSON{ | 
		
	
		
			
				|  |  |  |  |     Owner: ext.Owner, | 
		
	
		
			
				|  |  |  |  |     PendingOwner: ext.PendingOwner, | 
		
	
		
			
				|  |  |  |  |     Requirements: IDMap(ext.Requirements), | 
		
	
		
			
				|  |  |  |  |     Dependencies: IDMap(ext.Dependencies), | 
		
	
		
			
				|  |  |  |  |   }, "", "  ") | 
		
	
		
			
				|  |  |  |  |   return json.MarshalIndent(ext, "", "  ") | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func NewLockableExt() *LockableExt { | 
		
	
		
			
				|  |  |  |  |   return &LockableExt{ | 
		
	
		
			
				|  |  |  |  |     Owner: nil, | 
		
	
		
			
				|  |  |  |  |     PendingOwner: nil, | 
		
	
		
			
				|  |  |  |  |     Requirements: map[NodeID]ReqState{}, | 
		
	
		
			
				|  |  |  |  |     Dependencies: map[NodeID]string{}, | 
		
	
		
			
				|  |  |  |  |     Requirements: map[NodeID]LinkState{}, | 
		
	
		
			
				|  |  |  |  |     Dependencies: map[NodeID]LinkState{}, | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
	
		
			
				
					|  |  |  | @ -315,9 +317,9 @@ func (ext *LockableExt) HandleLockSignal(ctx *Context, source NodeID, node *Node | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func (ext *LockableExt) HandleLinkStartSignal(ctx *Context, source NodeID, node *Node, signal LinkStartSignal) { | 
		
	
		
			
				|  |  |  |  | func (ext *LockableExt) HandleLinkStartSignal(ctx *Context, source NodeID, node *Node, signal IDStateSignal) { | 
		
	
		
			
				|  |  |  |  |   ctx.Log.Logf("lockable", "LINK__START_SIGNAL: %s->%s %+v", source, node.ID, signal) | 
		
	
		
			
				|  |  |  |  |   link_type := signal.LinkType | 
		
	
		
			
				|  |  |  |  |   link_type := signal.State | 
		
	
		
			
				|  |  |  |  |   target := signal.ID | 
		
	
		
			
				|  |  |  |  |   switch link_type { | 
		
	
		
			
				|  |  |  |  |   case "req": | 
		
	
	
		
			
				
					|  |  |  | @ -340,8 +342,8 @@ func (ext *LockableExt) HandleLinkStartSignal(ctx *Context, source NodeID, node | 
		
	
		
			
				|  |  |  |  |     } else if dep_exists == true { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLinkStartSignal("already_dep", target)) | 
		
	
		
			
				|  |  |  |  |     } else { | 
		
	
		
			
				|  |  |  |  |       ext.Requirements[target] = ReqState{"linking", "unlocked"} | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, target, NewLinkSignal("link_as_req")) | 
		
	
		
			
				|  |  |  |  |       ext.Requirements[target] = LinkState{"linking", "unlocked", source} | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, target, NewLinkSignal("linked_as_req")) | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLinkStartSignal("linking_req", target)) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
	
		
			
				
					|  |  |  | @ -353,14 +355,14 @@ func (ext *LockableExt) HandleLinkSignal(ctx *Context, source NodeID, node *Node | 
		
	
		
			
				|  |  |  |  |   ctx.Log.Logf("lockable", "LINK_SIGNAL: %s->%s %+v", source, node.ID, signal) | 
		
	
		
			
				|  |  |  |  |   state := signal.State | 
		
	
		
			
				|  |  |  |  |   switch state { | 
		
	
		
			
				|  |  |  |  |   case "link_as_dep": | 
		
	
		
			
				|  |  |  |  |   case "linked_as_dep": | 
		
	
		
			
				|  |  |  |  |     state, exists := ext.Requirements[source] | 
		
	
		
			
				|  |  |  |  |     if exists == true && state.Link == "linked" { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLinkSignal("already_req")) | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, state.Initiator, NewLinkStartSignal("linked_as_req", source)) | 
		
	
		
			
				|  |  |  |  |     } else if state.Link == "linking" { | 
		
	
		
			
				|  |  |  |  |       state.Link = "linked" | 
		
	
		
			
				|  |  |  |  |       ext.Requirements[source] = state | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLinkSignal("linked_as_dep")) | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLinkSignal("linked_as_req")) | 
		
	
		
			
				|  |  |  |  |     } else if ext.PendingOwner != ext.Owner { | 
		
	
		
			
				|  |  |  |  |       if ext.Owner == nil { | 
		
	
		
			
				|  |  |  |  |         ctx.Send(node.ID, source, NewLinkSignal("locking")) | 
		
	
	
		
			
				
					|  |  |  | @ -368,17 +370,19 @@ func (ext *LockableExt) HandleLinkSignal(ctx *Context, source NodeID, node *Node | 
		
	
		
			
				|  |  |  |  |         ctx.Send(node.ID, source, NewLinkSignal("unlocking")) | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     } else { | 
		
	
		
			
				|  |  |  |  |       ext.Requirements[source] = ReqState{"linking", "unlocked"} | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLinkSignal("link_as_req")) | 
		
	
		
			
				|  |  |  |  |       ext.Requirements[source] = LinkState{"linking", "unlocked", source} | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLinkSignal("linked_as_req")) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     ctx.Log.Logf("lockable", "%s is a dependency of %s", node.ID, source) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   case "link_as_req": | 
		
	
		
			
				|  |  |  |  |   case "linked_as_req": | 
		
	
		
			
				|  |  |  |  |     state, exists := ext.Dependencies[source] | 
		
	
		
			
				|  |  |  |  |     if exists == true && state == "linked" { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLinkSignal("already_dep")) | 
		
	
		
			
				|  |  |  |  |     } else if state == "linking" { | 
		
	
		
			
				|  |  |  |  |       ext.Dependencies[source] = "linked" | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLinkSignal("linked_as_req")) | 
		
	
		
			
				|  |  |  |  |     if exists == true && state.Link == "linked" { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, state.Initiator, NewLinkStartSignal("linked_as_dep", source)) | 
		
	
		
			
				|  |  |  |  |     } else if state.Link == "linking" { | 
		
	
		
			
				|  |  |  |  |       state.Link = "linked" | 
		
	
		
			
				|  |  |  |  |       ext.Dependencies[source] = state | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLinkSignal("linked_as_dep")) | 
		
	
		
			
				|  |  |  |  |     } else if ext.PendingOwner != ext.Owner { | 
		
	
		
			
				|  |  |  |  |       if ext.Owner == nil { | 
		
	
		
			
				|  |  |  |  |         ctx.Send(node.ID, source, NewLinkSignal("locking")) | 
		
	
	
		
			
				
					|  |  |  | @ -386,28 +390,7 @@ func (ext *LockableExt) HandleLinkSignal(ctx *Context, source NodeID, node *Node | 
		
	
		
			
				|  |  |  |  |         ctx.Send(node.ID, source, NewLinkSignal("unlocking")) | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     } else { | 
		
	
		
			
				|  |  |  |  |       ext.Dependencies[source] = "linking" | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLinkSignal("link_as_dep")) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   case "linked_as_dep": | 
		
	
		
			
				|  |  |  |  |     state, exists := ext.Dependencies[source] | 
		
	
		
			
				|  |  |  |  |     if exists == false { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLinkSignal("not_linking")) | 
		
	
		
			
				|  |  |  |  |     } else if state == "linked" { | 
		
	
		
			
				|  |  |  |  |     } else if state == "linking" { | 
		
	
		
			
				|  |  |  |  |       ext.Dependencies[source] = "linked" | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLinkSignal("linked_as_req")) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     ctx.Log.Logf("lockable", "%s is a dependency of %s", node.ID, source) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   case "linked_as_req": | 
		
	
		
			
				|  |  |  |  |     state, exists := ext.Requirements[source] | 
		
	
		
			
				|  |  |  |  |     if exists == false { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLinkSignal("not_linking")) | 
		
	
		
			
				|  |  |  |  |     } else if state.Link == "linked" { | 
		
	
		
			
				|  |  |  |  |     } else if state.Link == "linking" { | 
		
	
		
			
				|  |  |  |  |       state.Link = "linked" | 
		
	
		
			
				|  |  |  |  |       ext.Requirements[source] = state | 
		
	
		
			
				|  |  |  |  |       ext.Dependencies[source] = LinkState{"linking", "unlocked", source} | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLinkSignal("linked_as_dep")) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     ctx.Log.Logf("lockable", "%s is a requirement of %s", node.ID, source) | 
		
	
	
		
			
				
					|  |  |  | @ -424,7 +407,7 @@ func (ext *LockableExt) Process(ctx *Context, source NodeID, node *Node, signal | 
		
	
		
			
				|  |  |  |  |   case Up: | 
		
	
		
			
				|  |  |  |  |     owner_sent := false | 
		
	
		
			
				|  |  |  |  |     for dependency, state := range(ext.Dependencies) { | 
		
	
		
			
				|  |  |  |  |       if state == "linked" { | 
		
	
		
			
				|  |  |  |  |       if state.Link == "linked" { | 
		
	
		
			
				|  |  |  |  |         err := ctx.Send(node.ID, dependency, signal) | 
		
	
		
			
				|  |  |  |  |         if err != nil { | 
		
	
		
			
				|  |  |  |  |           ctx.Log.Logf("signal", "LOCKABLE_SIGNAL_ERR: %s->%s - %e", node.ID, dependency, err) | 
		
	
	
		
			
				
					|  |  |  | @ -462,7 +445,7 @@ func (ext *LockableExt) Process(ctx *Context, source NodeID, node *Node, signal | 
		
	
		
			
				|  |  |  |  |     case LockSignalType: | 
		
	
		
			
				|  |  |  |  |       ext.HandleLockSignal(ctx, source, node, signal.(StateSignal)) | 
		
	
		
			
				|  |  |  |  |     case LinkStartSignalType: | 
		
	
		
			
				|  |  |  |  |       ext.HandleLinkStartSignal(ctx, source, node, signal.(LinkStartSignal)) | 
		
	
		
			
				|  |  |  |  |       ext.HandleLinkStartSignal(ctx, source, node, signal.(IDStateSignal)) | 
		
	
		
			
				|  |  |  |  |     default: | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   default: | 
		
	
	
		
			
				
					|  |  |  | 
 |