From e09482aa1fb1629a17deda28fffd7950d4aa9535 Mon Sep 17 00:00:00 2001 From: Eric H Date: Mon, 27 May 2024 20:36:41 -0400 Subject: [PATCH 1/9] Fix transition panic while currently in transition --- director.go | 23 ++++++++++++++++++++--- manager.go | 10 +++++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/director.go b/director.go index 1113868..28b9ac6 100644 --- a/director.go +++ b/director.go @@ -24,6 +24,23 @@ func NewSceneDirector[T any](scene Scene[T], state T, RuleSet map[Scene[T]][]Dir // ProcessTrigger finds if a transition should be triggered func (d *SceneDirector[T]) ProcessTrigger(trigger SceneTransitionTrigger) { + if prevTransition, ok := d.current.(SceneTransition[T]); ok { + // previous transition is still running, if related to trigger end it to start the next transition + isTransitionTrigger := false + for _, directiveList := range d.RuleSet { + for _, directive := range directiveList { + if directive.Trigger == trigger && prevTransition == directive.Transition { + isTransitionTrigger = true + break + } + } + } + if isTransitionTrigger { + prevTransition.End() + return + } + } + for _, directive := range d.RuleSet[d.current.(Scene[T])] { if directive.Trigger == trigger { if directive.Transition != nil { @@ -50,11 +67,11 @@ func (d *SceneDirector[T]) ProcessTrigger(trigger SceneTransitionTrigger) { } } -func (d *SceneDirector[T]) ReturnFromTransition(scene, orgin Scene[T]) { +func (d *SceneDirector[T]) ReturnFromTransition(scene, origin Scene[T]) { if c, ok := scene.(TransitionAwareScene[T]); ok { - c.PostTransition(orgin.Unload(), orgin) + c.PostTransition(origin.Unload(), origin) } else { - scene.Load(orgin.Unload(), d) + scene.Load(origin.Unload(), d) } d.current = scene } diff --git a/manager.go b/manager.go index b2f9bea..da1912f 100644 --- a/manager.go +++ b/manager.go @@ -21,6 +21,10 @@ func (s *SceneManager[T]) SwitchTo(scene Scene[T]) { } func (s *SceneManager[T]) SwitchWithTransition(scene Scene[T], transition SceneTransition[T]) { + if prevTransition, ok := s.current.(SceneTransition[T]); ok { + // previous transition is still running, end it to start the next transition + prevTransition.End() + } sc := s.current.(Scene[T]) transition.Start(sc, scene, s) if c, ok := sc.(TransitionAwareScene[T]); ok { @@ -31,11 +35,11 @@ func (s *SceneManager[T]) SwitchWithTransition(scene Scene[T], transition SceneT s.current = transition } -func (s *SceneManager[T]) ReturnFromTransition(scene, orgin Scene[T]) { +func (s *SceneManager[T]) ReturnFromTransition(scene, origin Scene[T]) { if c, ok := scene.(TransitionAwareScene[T]); ok { - c.PostTransition(orgin.Unload(), orgin) + c.PostTransition(origin.Unload(), origin) } else { - scene.Load(orgin.Unload(), s) + scene.Load(origin.Unload(), s) } s.current = scene } From 0269ac861dfde025b4263962b3b80fd2ea1eb461 Mon Sep 17 00:00:00 2001 From: Eric H Date: Mon, 27 May 2024 22:57:01 -0400 Subject: [PATCH 2/9] move early return to nested for loop instead --- director.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/director.go b/director.go index 28b9ac6..1722abc 100644 --- a/director.go +++ b/director.go @@ -26,19 +26,14 @@ func NewSceneDirector[T any](scene Scene[T], state T, RuleSet map[Scene[T]][]Dir func (d *SceneDirector[T]) ProcessTrigger(trigger SceneTransitionTrigger) { if prevTransition, ok := d.current.(SceneTransition[T]); ok { // previous transition is still running, if related to trigger end it to start the next transition - isTransitionTrigger := false for _, directiveList := range d.RuleSet { for _, directive := range directiveList { if directive.Trigger == trigger && prevTransition == directive.Transition { - isTransitionTrigger = true - break + prevTransition.End() + return } } } - if isTransitionTrigger { - prevTransition.End() - return - } } for _, directive := range d.RuleSet[d.current.(Scene[T])] { From f35dfcacae0fe940aeddf8622e7e871f0583a5b2 Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Tue, 28 May 2024 15:55:57 -0300 Subject: [PATCH 3/9] Adds fix to SwitchTo and misc --- manager.go | 4 ++++ transition.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/manager.go b/manager.go index da1912f..f6df33e 100644 --- a/manager.go +++ b/manager.go @@ -14,6 +14,10 @@ func NewSceneManager[T any](scene Scene[T], state T) *SceneManager[T] { // Scene Switching func (s *SceneManager[T]) SwitchTo(scene Scene[T]) { + if prevTransition, ok := s.current.(SceneTransition[T]); ok { + // previous transition is still running, end it first + prevTransition.End() + } if c, ok := s.current.(Scene[T]); ok { scene.Load(c.Unload(), s) s.current = scene diff --git a/transition.go b/transition.go index 01181ff..8ff2abc 100644 --- a/transition.go +++ b/transition.go @@ -50,7 +50,7 @@ func (t *BaseTransition[T]) Layout(outsideWidth, outsideHeight int) (int, int) { // Ends transition to the next scene func (t *BaseTransition[T]) End() { - t.sm.ReturnFromTransition(t.toScene.(Scene[T]), t.fromScene.(Scene[T])) + t.sm.ReturnFromTransition(t.toScene, t.fromScene) } type FadeTransition[T any] struct { From e7dc10bef528e9497732e0707bd6d8c592544050 Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Tue, 28 May 2024 17:01:34 -0300 Subject: [PATCH 4/9] Updates documentation --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 1479f39..07f67c1 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,10 @@ func (s *MyScene) Update() error { } ``` +## Acknowledgments + +- When switching scenes (i.e. calling `SwitchTo`, `SwitchWithTransition` or `ProcessTrigger`) while a transition is running it will immediately be canceled and the new switch will be started. To prevent this behavior use a TransitionAwareScene and prevent this methods to be called. + ## Contribution Contributions are welcome! If you find a bug or have a feature request, please open an issue on GitHub. If you would like to contribute code, please fork the repository and submit a pull request. From 46aff3825ce5cf88ca68608caa53077d073c1c7b Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Tue, 28 May 2024 17:02:06 -0300 Subject: [PATCH 5/9] Streamline director behaviour --- director.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/director.go b/director.go index 1722abc..166ea90 100644 --- a/director.go +++ b/director.go @@ -25,15 +25,8 @@ func NewSceneDirector[T any](scene Scene[T], state T, RuleSet map[Scene[T]][]Dir // ProcessTrigger finds if a transition should be triggered func (d *SceneDirector[T]) ProcessTrigger(trigger SceneTransitionTrigger) { if prevTransition, ok := d.current.(SceneTransition[T]); ok { - // previous transition is still running, if related to trigger end it to start the next transition - for _, directiveList := range d.RuleSet { - for _, directive := range directiveList { - if directive.Trigger == trigger && prevTransition == directive.Transition { - prevTransition.End() - return - } - } - } + // previous transition is still running, end it to process trigger + prevTransition.End() } for _, directive := range d.RuleSet[d.current.(Scene[T])] { From 5d5f883b3da84951b68c8a3579b9931f52b57ee6 Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Tue, 28 May 2024 17:02:40 -0300 Subject: [PATCH 6/9] Updates Tests --- director_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ transition_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/director_test.go b/director_test.go index f91d3bb..a131ca8 100644 --- a/director_test.go +++ b/director_test.go @@ -78,3 +78,48 @@ func TestSceneDirector_ProcessTriggerWithTransitionAwareness(t *testing.T) { rule.Transition.End() assert.Equal(t, rule.Dest, director.current) } + +func TestSceneDirector_ProcessTriggerCancelling(t *testing.T) { + mockScene := &MockScene{} + mockTransition := &baseTransitionImplementation{} + ruleSet := make(map[Scene[int]][]Directive[int]) + + director := NewSceneDirector[int](mockScene, 1, ruleSet) + + rule := Directive[int]{Dest: &MockScene{}, Trigger: 2, Transition: mockTransition} + ruleSet[mockScene] = []Directive[int]{rule} + director.ProcessTrigger(2) + + // Assert transition is running + assert.Equal(t, rule.Transition, director.current) + + director.ProcessTrigger(1) + assert.Equal(t, rule.Dest, director.current) +} + +func TestSceneDirector_ProcessTriggerCancellingToNewTransition(t *testing.T) { + mockSceneA := &MockScene{} + mockSceneB := &MockScene{} + mockTransitionA := &baseTransitionImplementation{} + mockTransitionB := &baseTransitionImplementation{} + ruleSet := make(map[Scene[int]][]Directive[int]) + + director := NewSceneDirector[int](mockSceneA, 1, ruleSet) + + ruleSet[mockSceneA] = []Directive[int]{ + Directive[int]{Dest: mockSceneB, Trigger: 2, Transition: mockTransitionA}, + } + ruleSet[mockSceneB] = []Directive[int]{ + Directive[int]{Dest: mockSceneA, Trigger: 2, Transition: mockTransitionB}, + } + director.ProcessTrigger(2) + + // Assert transition is running + assert.Equal(t, mockTransitionA, director.current) + + director.ProcessTrigger(2) + assert.Equal(t, mockTransitionB, director.current) + + mockTransitionB.End() + assert.Equal(t, mockSceneA, director.current) +} diff --git a/transition_test.go b/transition_test.go index 3c32e71..872dfc6 100644 --- a/transition_test.go +++ b/transition_test.go @@ -104,6 +104,35 @@ func TestBaseTransition_Awareness(t *testing.T) { assert.True(t, to.postTransitionCalled) } +func TestBaseTransition_SwitchCanceling(t *testing.T) { + from := &MockScene{} + to := &MockScene{} + trans := &baseTransitionImplementation{} + sm := NewSceneManager[int](from, 0) + sm.SwitchWithTransition(to, trans) + + // Assert transition is running + assert.Equal(t, trans, sm.current) + + sm.SwitchTo(to) + assert.Equal(t, to, sm.current) +} + +func TestBaseTransition_TransitionCanceling(t *testing.T) { + from := &MockScene{} + to := &MockScene{} + transA := &baseTransitionImplementation{} + transB := &baseTransitionImplementation{} + sm := NewSceneManager[int](from, 0) + sm.SwitchWithTransition(to, transA) + + // Assert transition is running + assert.Equal(t, transA, sm.current) + + sm.SwitchWithTransition(to, transB) + assert.Equal(t, transB, sm.current) +} + func TestFadeTransition_UpdateOncePerFrame(t *testing.T) { var value float32 = .6 from := &MockScene{} From 9c985856527eaf347b9cfa380447fbc66913595c Mon Sep 17 00:00:00 2001 From: Eric H Date: Tue, 28 May 2024 20:48:46 -0400 Subject: [PATCH 7/9] Update director example with 3rd scene to verify transitions - 1st scene: Red, 2nd scene: Blue, 3rd scene Green --- examples/director/main.go | 43 ++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/examples/director/main.go b/examples/director/main.go index 294086d..1b79569 100644 --- a/examples/director/main.go +++ b/examples/director/main.go @@ -81,6 +81,25 @@ func (s *SecondScene) Draw(screen *ebiten.Image) { ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Count: %v, WindowSize: %s", s.count, s.bounds.Max), s.bounds.Dx()/2, s.bounds.Dy()/2) } +type ThirdScene struct { + BaseScene +} + +func (s *ThirdScene) Update() error { + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { + s.count-- + } + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { + s.sm.ProcessTrigger(Trigger) + } + return nil +} + +func (s *ThirdScene) Draw(screen *ebiten.Image) { + screen.Fill(color.RGBA{0, 255, 0, 255}) // Fill Green + ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Count: %v, WindowSize: %s", s.count, s.bounds.Max), s.bounds.Dx()/2, s.bounds.Dy()/2) +} + func main() { ebiten.SetWindowSize(screenWidth, screenHeight) ebiten.SetWindowTitle("My Game") @@ -90,16 +109,30 @@ func main() { s1 := &FirstScene{} s2 := &SecondScene{} - trans := stagehand.NewSlideTransition[State](stagehand.BottomToTop, 0.05) + s3 := &ThirdScene{} + trans := stagehand.NewSlideTransition[State](stagehand.BottomToTop, 0.02) + trans2 := stagehand.NewSlideTransition[State](stagehand.TopToBottom, 0.02) + trans3 := stagehand.NewSlideTransition[State](stagehand.LeftToRight, 0.02) rs := map[stagehand.Scene[State]][]stagehand.Directive[State]{ - s1: []stagehand.Directive[State]{ - stagehand.Directive[State]{Dest: s2, Trigger: Trigger}, + s1: { + stagehand.Directive[State]{ + Dest: s2, + Trigger: Trigger, + Transition: trans, + }, + }, + s2: { + stagehand.Directive[State]{ + Dest: s3, + Trigger: Trigger, + Transition: trans2, + }, }, - s2: []stagehand.Directive[State]{ + s3: { stagehand.Directive[State]{ Dest: s1, Trigger: Trigger, - Transition: trans, + Transition: trans3, }, }, } From 76bc96b8b3b17a2778b90f6fd3c9d0d78d550855 Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Thu, 30 May 2024 15:58:10 -0300 Subject: [PATCH 8/9] Update director example --- examples/director/main.go | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/examples/director/main.go b/examples/director/main.go index 1b79569..4c01b8a 100644 --- a/examples/director/main.go +++ b/examples/director/main.go @@ -24,9 +24,10 @@ const ( ) type BaseScene struct { - bounds image.Rectangle - count State - sm *stagehand.SceneDirector[State] + bounds image.Rectangle + count State + Condition State + sm *stagehand.SceneDirector[State] } func (s *BaseScene) Layout(w, h int) (int, int) { @@ -51,7 +52,7 @@ func (s *FirstScene) Update() error { if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { s.count++ } - if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) && s.count == s.Condition { s.sm.ProcessTrigger(Trigger) } return nil @@ -59,7 +60,7 @@ func (s *FirstScene) Update() error { func (s *FirstScene) Draw(screen *ebiten.Image) { screen.Fill(color.RGBA{255, 0, 0, 255}) // Fill Red - ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Count: %v, WindowSize: %s", s.count, s.bounds.Max), s.bounds.Dx()/2, s.bounds.Dy()/2) + ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Count: %v, WindowSize: %s\nCan Switch? %v", s.count, s.bounds.Max, s.count == s.Condition), s.bounds.Dx()/2, s.bounds.Dy()/2) } type SecondScene struct { @@ -70,7 +71,7 @@ func (s *SecondScene) Update() error { if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { s.count-- } - if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) && s.count <= s.Condition { s.sm.ProcessTrigger(Trigger) } return nil @@ -78,7 +79,7 @@ func (s *SecondScene) Update() error { func (s *SecondScene) Draw(screen *ebiten.Image) { screen.Fill(color.RGBA{0, 0, 255, 255}) // Fill Blue - ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Count: %v, WindowSize: %s", s.count, s.bounds.Max), s.bounds.Dx()/2, s.bounds.Dy()/2) + ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Count: %v, WindowSize: %s\nCan Switch? %v", s.count, s.bounds.Max, s.count <= s.Condition), s.bounds.Dx()/2, s.bounds.Dy()/2) } type ThirdScene struct { @@ -87,9 +88,9 @@ type ThirdScene struct { func (s *ThirdScene) Update() error { if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { - s.count-- + s.count++ } - if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) && s.count >= s.Condition { s.sm.ProcessTrigger(Trigger) } return nil @@ -97,7 +98,7 @@ func (s *ThirdScene) Update() error { func (s *ThirdScene) Draw(screen *ebiten.Image) { screen.Fill(color.RGBA{0, 255, 0, 255}) // Fill Green - ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Count: %v, WindowSize: %s", s.count, s.bounds.Max), s.bounds.Dx()/2, s.bounds.Dy()/2) + ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Count: %v, WindowSize: %s\nCan Switch? %v", s.count, s.bounds.Max, s.count >= s.Condition), s.bounds.Dx()/2, s.bounds.Dy()/2) } func main() { @@ -107,9 +108,9 @@ func main() { state := State(10) - s1 := &FirstScene{} - s2 := &SecondScene{} - s3 := &ThirdScene{} + s1 := &FirstScene{BaseScene{Condition: 10}} + s2 := &SecondScene{BaseScene{Condition: 5}} + s3 := &ThirdScene{BaseScene{Condition: 15}} trans := stagehand.NewSlideTransition[State](stagehand.BottomToTop, 0.02) trans2 := stagehand.NewSlideTransition[State](stagehand.TopToBottom, 0.02) trans3 := stagehand.NewSlideTransition[State](stagehand.LeftToRight, 0.02) From eef3250aba8105d425e9c315b36d4a6716f8d0f2 Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Sat, 1 Jun 2024 10:22:20 -0300 Subject: [PATCH 9/9] Simplify director example --- examples/director/main.go | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/examples/director/main.go b/examples/director/main.go index 4c01b8a..d0670eb 100644 --- a/examples/director/main.go +++ b/examples/director/main.go @@ -24,10 +24,10 @@ const ( ) type BaseScene struct { - bounds image.Rectangle - count State - Condition State - sm *stagehand.SceneDirector[State] + bounds image.Rectangle + count State + active bool + sm *stagehand.SceneDirector[State] } func (s *BaseScene) Layout(w, h int) (int, int) { @@ -37,10 +37,12 @@ func (s *BaseScene) Layout(w, h int) (int, int) { func (s *BaseScene) Load(st State, sm stagehand.SceneController[State]) { s.count = st + s.active = true s.sm = sm.(*stagehand.SceneDirector[State]) } func (s *BaseScene) Unload() State { + s.active = false return s.count } @@ -52,7 +54,7 @@ func (s *FirstScene) Update() error { if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { s.count++ } - if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) && s.count == s.Condition { + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) && s.active { s.sm.ProcessTrigger(Trigger) } return nil @@ -60,7 +62,7 @@ func (s *FirstScene) Update() error { func (s *FirstScene) Draw(screen *ebiten.Image) { screen.Fill(color.RGBA{255, 0, 0, 255}) // Fill Red - ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Count: %v, WindowSize: %s\nCan Switch? %v", s.count, s.bounds.Max, s.count == s.Condition), s.bounds.Dx()/2, s.bounds.Dy()/2) + ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Count: %v, WindowSize: %s\nActive? %v", s.count, s.bounds.Max, s.active), s.bounds.Dx()/2, s.bounds.Dy()/2) } type SecondScene struct { @@ -71,7 +73,7 @@ func (s *SecondScene) Update() error { if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { s.count-- } - if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) && s.count <= s.Condition { + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) && s.active { s.sm.ProcessTrigger(Trigger) } return nil @@ -79,7 +81,7 @@ func (s *SecondScene) Update() error { func (s *SecondScene) Draw(screen *ebiten.Image) { screen.Fill(color.RGBA{0, 0, 255, 255}) // Fill Blue - ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Count: %v, WindowSize: %s\nCan Switch? %v", s.count, s.bounds.Max, s.count <= s.Condition), s.bounds.Dx()/2, s.bounds.Dy()/2) + ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Count: %v, WindowSize: %s\nActive? %v", s.count, s.bounds.Max, s.active), s.bounds.Dx()/2, s.bounds.Dy()/2) } type ThirdScene struct { @@ -90,7 +92,7 @@ func (s *ThirdScene) Update() error { if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { s.count++ } - if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) && s.count >= s.Condition { + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) && s.active { s.sm.ProcessTrigger(Trigger) } return nil @@ -98,7 +100,7 @@ func (s *ThirdScene) Update() error { func (s *ThirdScene) Draw(screen *ebiten.Image) { screen.Fill(color.RGBA{0, 255, 0, 255}) // Fill Green - ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Count: %v, WindowSize: %s\nCan Switch? %v", s.count, s.bounds.Max, s.count >= s.Condition), s.bounds.Dx()/2, s.bounds.Dy()/2) + ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Count: %v, WindowSize: %s\nActive? %v", s.count, s.bounds.Max, s.active), s.bounds.Dx()/2, s.bounds.Dy()/2) } func main() { @@ -108,9 +110,9 @@ func main() { state := State(10) - s1 := &FirstScene{BaseScene{Condition: 10}} - s2 := &SecondScene{BaseScene{Condition: 5}} - s3 := &ThirdScene{BaseScene{Condition: 15}} + s1 := &FirstScene{} + s2 := &SecondScene{} + s3 := &ThirdScene{} trans := stagehand.NewSlideTransition[State](stagehand.BottomToTop, 0.02) trans2 := stagehand.NewSlideTransition[State](stagehand.TopToBottom, 0.02) trans3 := stagehand.NewSlideTransition[State](stagehand.LeftToRight, 0.02)