From 62dbe54e81d310d7f9b9a5de33e417fb47c9e1a6 Mon Sep 17 00:00:00 2001 From: Noah Metz Date: Tue, 30 May 2023 20:45:16 -0600 Subject: [PATCH] Cleaned up Event to make new structures easier, added vex Match and tests --- event.go | 115 ++++++++++++++++++++------------------------ main.go | 24 +++++----- manager.go | 2 +- vex.go | 90 ++++++++++++++++++++++++++++++++-- vex_test.go | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 288 insertions(+), 79 deletions(-) create mode 100644 vex_test.go diff --git a/event.go b/event.go index 77ff38d..691fa73 100644 --- a/event.go +++ b/event.go @@ -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 - } - - // Check signals - select { - case reason := <-event.abort: - error_str := fmt.Sprintf("State Machine aborted: %s", reason) - return errors.New(error_str) - default: + } 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 + } + } 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 } diff --git a/main.go b/main.go index e4995ee..74ba9f9 100644 --- a/main.go +++ b/main.go @@ -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) diff --git a/manager.go b/manager.go index 7a93895..c917c63 100644 --- a/manager.go +++ b/manager.go @@ -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{ diff --git a/vex.go b/vex.go index 768466f..5c5d4f4 100644 --- a/vex.go +++ b/vex.go @@ -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 +} diff --git a/vex_test.go b/vex_test.go new file mode 100644 index 0000000..5103e26 --- /dev/null +++ b/vex_test.go @@ -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) +}