Cleaned up Event to make new structures easier, added vex Match and tests

graph-rework
noah metz 2023-05-30 20:45:16 -06:00
parent 07097bfd9d
commit 62dbe54e81
5 changed files with 288 additions and 79 deletions

@ -56,8 +56,9 @@ type Event interface {
FindChild(id graphql.ID) Event
Run() error
Abort() error
Lock() error
Unlock() error
Signal(action string) error
LockResources() error
Finish() error
}
// BaseEvent is the most basic event that can exist in the event tree.
@ -72,26 +73,26 @@ type BaseEvent struct {
required_resources []Resource
children []Event
child_info map[Event]EventInfo
actions map[string]func() (string, error)
actions map[string]func(Event) (string, error)
parent Event
signal chan string
abort chan string
}
func (event * BaseEvent) Abort() error {
for _, event := range(event.Children()) {
event.Abort()
}
event.signal <- "abort"
return nil
}
func (queue * EventQueue) Abort() error {
for _, event := range(queue.Children()) {
event.Abort()
}
queue.signal <- "abort"
func (event * BaseEvent) Signal(action string) error {
event.signal <- action
return nil
}
func (event * BaseEvent) Lock() error {
func (event * BaseEvent) LockResources() error {
locked_resources := []Resource{}
lock_err := false
for _, resource := range(event.RequiredResources()) {
@ -111,7 +112,7 @@ func (event * BaseEvent) Lock() error {
return nil
}
func (event * BaseEvent) Unlock() error {
func (event * BaseEvent) Finish() error {
for _, resource := range(event.RequiredResources()) {
err := resource.Unlock(event)
if err != nil {
@ -121,6 +122,10 @@ func (event * BaseEvent) Unlock() error {
return event.DoneResource().Unlock(event)
}
func (event * BaseEvent) LockDone() {
event.DoneResource().Lock(event)
}
func (event * BaseEvent) Run() error {
next_action := "start"
var err error = nil
@ -133,17 +138,19 @@ func (event * BaseEvent) Run() error {
}
// Run the edge function
next_action, err = action()
next_action, err = action(event)
if err != nil {
return err
} else if next_action == "wait" {
// Wait for an external signal to set the next_action
signal := <- event.signal
if signal == "abort" {
return errors.New("State Machine aborted by signal")
} else {
next_action = signal
}
// Check signals
select {
case reason := <-event.abort:
error_str := fmt.Sprintf("State Machine aborted: %s", reason)
return errors.New(error_str)
default:
} else {
// next_action is already set correctly
}
// Update the event after running the edge
@ -163,9 +170,9 @@ type EventQueue struct {
BaseEvent
}
func NewEvent(name string, description string, required_resources []Resource) (* BaseEvent) {
func NewBaseEvent(name string, description string, required_resources []Resource) (BaseEvent) {
done_resource := NewResource("event_done", "signal that event is done", []Resource{})
event := &BaseEvent{
event := BaseEvent{
BaseNode: BaseNode{
name: name,
description: description,
@ -177,49 +184,41 @@ func NewEvent(name string, description string, required_resources []Resource) (*
child_info: map[Event]EventInfo{},
done_resource: done_resource,
required_resources: required_resources,
actions: map[string]func()(string, error){},
actions: map[string]func(Event)(string, error){},
signal: make(chan string, 10),
abort: make(chan string, 1),
}
return event
}
func NewEvent(name string, description string, required_resources []Resource) (* BaseEvent) {
event := NewBaseEvent(name, description, required_resources)
event_ptr := &event
// Lock the done_resource by default
done_resource.Lock(event)
event.LockDone()
event.actions["start"] = func() (string, error) {
event_ptr.actions["start"] = func(event Event) (string, error) {
return "", nil
}
return event
return event_ptr
}
func NewEventQueue(name string, description string, required_resources []Resource) (* EventQueue) {
done_resource := NewResource("event_done", "signal that event is done", []Resource{})
queue := &EventQueue{
BaseEvent: BaseEvent{
BaseNode: BaseNode{
name: name,
description: description,
id: gql_randid(),
listeners: []chan error{},
},
parent: nil,
children: []Event{},
child_info: map[Event]EventInfo{},
done_resource: done_resource,
required_resources: required_resources,
actions: map[string]func()(string, error){},
signal: make(chan string, 10),
abort: make(chan string, 1),
},
BaseEvent: NewBaseEvent(name, description, []Resource{}),
}
// Need to lock it with th BaseEvent since Unlock is implemented on the BaseEvent
done_resource.Lock(&queue.BaseEvent)
queue.LockDone()
queue.actions["start"] = func() (string, error) {
queue.actions["start"] = func(queue Event) (string, error) {
return "queue_event", nil
}
queue.actions["queue_event"] = func() (string, error) {
queue.actions["queue_event"] = func(queue Event) (string, error) {
// Copy the events to sort the list
copied_events := make([]Event, len(queue.Children()))
copy(copied_events, queue.Children())
@ -236,17 +235,17 @@ func NewEventQueue(name string, description string, required_resources []Resourc
if info.state == "queued" {
wait = true
// Try to lock it
err := event.Lock()
err := event.LockResources()
// start in new goroutine
if err != nil {
} else {
info.state = "running"
go func(event Event, info * EventQueueInfo, queue * EventQueue) {
go func(event Event, info * EventQueueInfo, queue Event) {
event.Run()
info.state = "done"
event.Unlock()
queue.signal <- "event_done"
event.Finish()
queue.Signal("event_done")
}(event, info, queue)
}
} else if info.state == "running" {
@ -261,19 +260,11 @@ func NewEventQueue(name string, description string, required_resources []Resourc
}
}
queue.actions["wait"] = func() (string, error) {
// Wait until signaled by a thread
/*
What signals to take action for:
- abort : sent by any other thread : abort any child events and set the next event to none
- resource_available : sent by the aggregator goroutine when the lock on a resource changes : see if any events can be locked
- event_done : sent by child event threads : see if all events are completed
*/
signal := <- queue.signal
if signal == "abort" {
queue.abort <- "aborted by signal"
return "", nil
queue.actions["event_done"] = func(queue Event) (string, error) {
return "queue_event", nil
}
queue.actions["resource_available"] = func(queue Event) (string, error) {
return "queue_event", nil
}

@ -8,18 +8,18 @@ func fake_data() * EventManager {
resources := []Resource{}
teams := []*Team{}
teams = append(teams, NewTeam("6659", "A", []string{"jimmy"}))
teams = append(teams, NewTeam("6659", "B", []string{"timmy"}))
teams = append(teams, NewTeam("6659", "C", []string{"grace"}))
teams = append(teams, NewTeam("6659", "D", []string{"jeremy"}))
teams = append(teams, NewTeam("210", "W", []string{"bobby"}))
teams = append(teams, NewTeam("210", "X", []string{"toby"}))
teams = append(teams, NewTeam("210", "Y", []string{"jennifer"}))
teams = append(teams, NewTeam("210", "Z", []string{"emily"}))
teams = append(teams, NewTeam("315", "W", []string{"bobby"}))
teams = append(teams, NewTeam("315", "X", []string{"toby"}))
teams = append(teams, NewTeam("315", "Y", []string{"jennifer"}))
teams = append(teams, NewTeam("315", "Z", []string{"emily"}))
teams = append(teams, NewTeam("6659", "A", []*Member{NewMember("jimmy")}))
teams = append(teams, NewTeam("6659", "B", []*Member{NewMember("timmy")}))
teams = append(teams, NewTeam("6659", "C", []*Member{NewMember("grace")}))
teams = append(teams, NewTeam("6659", "D", []*Member{NewMember("jeremy")}))
teams = append(teams, NewTeam("210", "W", []*Member{NewMember("bobby")}))
teams = append(teams, NewTeam("210", "X", []*Member{NewMember("toby")}))
teams = append(teams, NewTeam("210", "Y", []*Member{NewMember("jennifer")}))
teams = append(teams, NewTeam("210", "Z", []*Member{NewMember("emily")}))
teams = append(teams, NewTeam("315", "W", []*Member{NewMember("bobby")}))
teams = append(teams, NewTeam("315", "X", []*Member{NewMember("toby")}))
teams = append(teams, NewTeam("315", "Y", []*Member{NewMember("jennifer")}))
teams = append(teams, NewTeam("315", "Z", []*Member{NewMember("emily")}))
for _, team := range teams {
resources = append(resources, team)

@ -12,7 +12,7 @@ type EventManager struct {
root_event Event
}
// root_event's requirements must be in dag_nodes, and dag_nodes must be ordered by dependency(no children first)
// root_event's requirements must be in dag_nodes, and dag_nodes must be ordered by dependency(children first)
func NewEventManager(root_event Event, dag_nodes []Resource) * EventManager {
manager := &EventManager{

@ -4,14 +4,74 @@ import (
"fmt"
)
type ArenaDriver interface {
}
type VirtualArenaDriver struct {
}
type Arena struct {
BaseResource
driver ArenaDriver
}
func NewVirtualArena(name string) * Arena {
arena := &Arena{
BaseResource: BaseResource{
BaseNode: BaseNode{
name: name,
description: "A virtual vex arena",
id: gql_randid(),
listeners: []chan error{},
},
parents: []Resource{},
children: []Resource{},
},
driver: VirtualArenaDriver{},
}
return arena
}
type Member struct {
BaseResource
}
func NewMember(name string) * Member {
member := &Member{
BaseResource: BaseResource{
BaseNode: BaseNode{
name: name,
description: "A Team Member",
id: gql_randid(),
listeners: []chan error{},
},
parents: []Resource{},
children: []Resource{},
},
}
return member
}
type Team struct {
BaseResource
Members []string
Org string
Team string
}
func NewTeam(org string, team string, members []string) * Team {
func (team * Team) Members() []*Member {
ret := make([]*Member, len(team.children))
for idx, member := range(team.children) {
ret[idx] = member.(*Member)
}
return ret
}
func NewTeam(org string, team string, members []*Member) * Team {
name := fmt.Sprintf("%s%s", org, team)
description := fmt.Sprintf("Team %s", name)
resource := &Team{
@ -23,12 +83,14 @@ func NewTeam(org string, team string, members []string) * Team {
listeners: []chan error{},
},
parents: []Resource{},
children: []Resource{},
children: make([]Resource, len(members)),
},
Members: members,
Org: org,
Team: team,
}
for idx, member := range(members) {
resource.children[idx] = member
}
return resource
}
@ -54,3 +116,23 @@ func NewAlliance(team0 * Team, team1 * Team) * Alliance {
}
return resource
}
type Match struct {
BaseEvent
}
func NewMatch(alliance0 * Alliance, alliance1 * Alliance, arena * Arena) * Match {
name := fmt.Sprintf("Match: %s vs. %s", alliance0.Name(), alliance1.Name() )
description := "A vex match"
match := &Match{
BaseEvent: NewBaseEvent(name, description, []Resource{alliance0, alliance1, arena}),
}
match.LockDone()
match.actions["start"] = func(match Event) (string, error) {
return "", nil
}
return match
}

@ -0,0 +1,136 @@
package main
import (
"testing"
"fmt"
)
type vex_tester graph_tester
func TestNewMemberAdd(t *testing.T) {
name := "Noah"
member := NewMember(name)
root_event := NewEvent("", "", []Resource{member})
event_manager := NewEventManager(root_event, []Resource{member})
res := event_manager.FindResource(member.ID())
if res == nil {
t.Fatal("Failed to find member in event_manager")
}
if res.Name() != name || res.ID() != member.ID() {
t.Fatal("Name/ID of returned resource did not match")
}
}
func TestNewTeamAdd(t *testing.T) {
name_1 := "Noah"
name_2 := "Ben"
org := "6659"
team_id := "S"
member_1 := NewMember(name_1)
member_2 := NewMember(name_2)
team := NewTeam(org, team_id, []*Member{member_1, member_2})
root_event := NewEvent("", "", []Resource{team})
event_manager := NewEventManager(root_event, []Resource{member_1, member_2, team})
res := event_manager.FindResource(team.ID())
if res == nil {
t.Fatal("Failed to find team in event_manager")
}
if res.Name() != fmt.Sprintf("%s%s", org, team_id) || res.ID() != team.ID() {
t.Fatal("Name/ID of returned team did not match")
}
if res.Children()[0].ID() != member_1.ID() && res.Children()[1].ID() != member_1.ID() {
t.Fatal("Could not find member_1 in team")
}
if res.Children()[0].ID() != member_2.ID() && res.Children()[1].ID() != member_2.ID() {
t.Fatal("Could not find member_2 in team")
}
res = event_manager.FindResource(member_1.ID())
if res == nil {
t.Fatal("Failed to find member_1 in event_manager")
}
if res.Name() != name_1 || res.ID() != member_1.ID() {
t.Fatal("Name/ID of returned member_1 did not match")
}
}
func TestNewAllianceAdd(t *testing.T) {
name_1 := "Noah"
name_2 := "Ben"
org_1 := "6659"
team_id_1 := "S"
org_2 := "210"
team_id_2 := "Y"
member_1 := NewMember(name_1)
member_2 := NewMember(name_2)
team_1 := NewTeam(org_1, team_id_1, []*Member{member_1})
team_2 := NewTeam(org_2, team_id_2, []*Member{member_2})
alliance := NewAlliance(team_1, team_2)
root_event := NewEvent("", "", []Resource{alliance})
event_manager := NewEventManager(root_event, []Resource{member_1, member_2, team_1, team_2, alliance})
res := event_manager.FindResource(alliance.ID())
if res == nil {
t.Fatal("Failed to find alliance in event_manager")
}
if res.Name() != fmt.Sprintf("Alliance %s/%s", team_1.Name(), team_2.Name()) || res.ID() != alliance.ID() {
t.Fatal("Name/ID of returned alliance did not match")
}
}
func TestNewMatchAdd(t *testing.T) {
name_1 := "Noah"
name_2 := "Ben"
name_3 := "Justin"
name_4 := "Ian"
org_1 := "6659"
org_2 := "210"
team_id_1 := "S"
team_id_2 := "Y"
arena_name := "Center Arena"
member_1 := NewMember(name_1)
member_2 := NewMember(name_2)
member_3 := NewMember(name_3)
member_4 := NewMember(name_4)
team_1 := NewTeam(org_1, team_id_1, []*Member{member_1})
team_2 := NewTeam(org_1, team_id_2, []*Member{member_2})
team_3 := NewTeam(org_2, team_id_1, []*Member{member_3})
team_4 := NewTeam(org_2, team_id_2, []*Member{member_4})
alliance_1 := NewAlliance(team_1, team_2)
alliance_2 := NewAlliance(team_3, team_4)
arena := NewVirtualArena(arena_name)
root_event := NewMatch(alliance_1, alliance_2, arena)
event_manager := NewEventManager(root_event, []Resource{member_1, member_2, member_3, member_4, team_1, team_2, team_3, team_4, alliance_1, alliance_2, arena})
println(event_manager)
}