Skip to content

Commit 577c465

Browse files
authored
Merge pull request #8 from harbdog/fix-transition-panic
Fix transition panic while currently in transition
2 parents d54b85e + eef3250 commit 577c465

File tree

7 files changed

+143
-16
lines changed

7 files changed

+143
-16
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,10 @@ func (s *MyScene) Update() error {
245245
}
246246
```
247247

248+
## Acknowledgments
249+
250+
- 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.
251+
248252
## Contribution
249253

250254
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.

director.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ func NewSceneDirector[T any](scene Scene[T], state T, RuleSet map[Scene[T]][]Dir
2424

2525
// ProcessTrigger finds if a transition should be triggered
2626
func (d *SceneDirector[T]) ProcessTrigger(trigger SceneTransitionTrigger) {
27+
if prevTransition, ok := d.current.(SceneTransition[T]); ok {
28+
// previous transition is still running, end it to process trigger
29+
prevTransition.End()
30+
}
31+
2732
for _, directive := range d.RuleSet[d.current.(Scene[T])] {
2833
if directive.Trigger == trigger {
2934
if directive.Transition != nil {
@@ -50,11 +55,11 @@ func (d *SceneDirector[T]) ProcessTrigger(trigger SceneTransitionTrigger) {
5055
}
5156
}
5257

53-
func (d *SceneDirector[T]) ReturnFromTransition(scene, orgin Scene[T]) {
58+
func (d *SceneDirector[T]) ReturnFromTransition(scene, origin Scene[T]) {
5459
if c, ok := scene.(TransitionAwareScene[T]); ok {
55-
c.PostTransition(orgin.Unload(), orgin)
60+
c.PostTransition(origin.Unload(), origin)
5661
} else {
57-
scene.Load(orgin.Unload(), d)
62+
scene.Load(origin.Unload(), d)
5863
}
5964
d.current = scene
6065
}

director_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,48 @@ func TestSceneDirector_ProcessTriggerWithTransitionAwareness(t *testing.T) {
7878
rule.Transition.End()
7979
assert.Equal(t, rule.Dest, director.current)
8080
}
81+
82+
func TestSceneDirector_ProcessTriggerCancelling(t *testing.T) {
83+
mockScene := &MockScene{}
84+
mockTransition := &baseTransitionImplementation{}
85+
ruleSet := make(map[Scene[int]][]Directive[int])
86+
87+
director := NewSceneDirector[int](mockScene, 1, ruleSet)
88+
89+
rule := Directive[int]{Dest: &MockScene{}, Trigger: 2, Transition: mockTransition}
90+
ruleSet[mockScene] = []Directive[int]{rule}
91+
director.ProcessTrigger(2)
92+
93+
// Assert transition is running
94+
assert.Equal(t, rule.Transition, director.current)
95+
96+
director.ProcessTrigger(1)
97+
assert.Equal(t, rule.Dest, director.current)
98+
}
99+
100+
func TestSceneDirector_ProcessTriggerCancellingToNewTransition(t *testing.T) {
101+
mockSceneA := &MockScene{}
102+
mockSceneB := &MockScene{}
103+
mockTransitionA := &baseTransitionImplementation{}
104+
mockTransitionB := &baseTransitionImplementation{}
105+
ruleSet := make(map[Scene[int]][]Directive[int])
106+
107+
director := NewSceneDirector[int](mockSceneA, 1, ruleSet)
108+
109+
ruleSet[mockSceneA] = []Directive[int]{
110+
Directive[int]{Dest: mockSceneB, Trigger: 2, Transition: mockTransitionA},
111+
}
112+
ruleSet[mockSceneB] = []Directive[int]{
113+
Directive[int]{Dest: mockSceneA, Trigger: 2, Transition: mockTransitionB},
114+
}
115+
director.ProcessTrigger(2)
116+
117+
// Assert transition is running
118+
assert.Equal(t, mockTransitionA, director.current)
119+
120+
director.ProcessTrigger(2)
121+
assert.Equal(t, mockTransitionB, director.current)
122+
123+
mockTransitionB.End()
124+
assert.Equal(t, mockSceneA, director.current)
125+
}

examples/director/main.go

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const (
2626
type BaseScene struct {
2727
bounds image.Rectangle
2828
count State
29+
active bool
2930
sm *stagehand.SceneDirector[State]
3031
}
3132

@@ -36,10 +37,12 @@ func (s *BaseScene) Layout(w, h int) (int, int) {
3637

3738
func (s *BaseScene) Load(st State, sm stagehand.SceneController[State]) {
3839
s.count = st
40+
s.active = true
3941
s.sm = sm.(*stagehand.SceneDirector[State])
4042
}
4143

4244
func (s *BaseScene) Unload() State {
45+
s.active = false
4346
return s.count
4447
}
4548

@@ -51,15 +54,15 @@ func (s *FirstScene) Update() error {
5154
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
5255
s.count++
5356
}
54-
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) {
57+
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) && s.active {
5558
s.sm.ProcessTrigger(Trigger)
5659
}
5760
return nil
5861
}
5962

6063
func (s *FirstScene) Draw(screen *ebiten.Image) {
6164
screen.Fill(color.RGBA{255, 0, 0, 255}) // Fill Red
62-
ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Count: %v, WindowSize: %s", s.count, s.bounds.Max), s.bounds.Dx()/2, s.bounds.Dy()/2)
65+
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)
6366
}
6467

6568
type SecondScene struct {
@@ -70,15 +73,34 @@ func (s *SecondScene) Update() error {
7073
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
7174
s.count--
7275
}
73-
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) {
76+
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) && s.active {
7477
s.sm.ProcessTrigger(Trigger)
7578
}
7679
return nil
7780
}
7881

7982
func (s *SecondScene) Draw(screen *ebiten.Image) {
8083
screen.Fill(color.RGBA{0, 0, 255, 255}) // Fill Blue
81-
ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Count: %v, WindowSize: %s", s.count, s.bounds.Max), s.bounds.Dx()/2, s.bounds.Dy()/2)
84+
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)
85+
}
86+
87+
type ThirdScene struct {
88+
BaseScene
89+
}
90+
91+
func (s *ThirdScene) Update() error {
92+
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
93+
s.count++
94+
}
95+
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) && s.active {
96+
s.sm.ProcessTrigger(Trigger)
97+
}
98+
return nil
99+
}
100+
101+
func (s *ThirdScene) Draw(screen *ebiten.Image) {
102+
screen.Fill(color.RGBA{0, 255, 0, 255}) // Fill Green
103+
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)
82104
}
83105

84106
func main() {
@@ -90,16 +112,30 @@ func main() {
90112

91113
s1 := &FirstScene{}
92114
s2 := &SecondScene{}
93-
trans := stagehand.NewSlideTransition[State](stagehand.BottomToTop, 0.05)
115+
s3 := &ThirdScene{}
116+
trans := stagehand.NewSlideTransition[State](stagehand.BottomToTop, 0.02)
117+
trans2 := stagehand.NewSlideTransition[State](stagehand.TopToBottom, 0.02)
118+
trans3 := stagehand.NewSlideTransition[State](stagehand.LeftToRight, 0.02)
94119
rs := map[stagehand.Scene[State]][]stagehand.Directive[State]{
95-
s1: []stagehand.Directive[State]{
96-
stagehand.Directive[State]{Dest: s2, Trigger: Trigger},
120+
s1: {
121+
stagehand.Directive[State]{
122+
Dest: s2,
123+
Trigger: Trigger,
124+
Transition: trans,
125+
},
97126
},
98-
s2: []stagehand.Directive[State]{
127+
s2: {
128+
stagehand.Directive[State]{
129+
Dest: s3,
130+
Trigger: Trigger,
131+
Transition: trans2,
132+
},
133+
},
134+
s3: {
99135
stagehand.Directive[State]{
100136
Dest: s1,
101137
Trigger: Trigger,
102-
Transition: trans,
138+
Transition: trans3,
103139
},
104140
},
105141
}

manager.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,21 @@ func NewSceneManager[T any](scene Scene[T], state T) *SceneManager[T] {
1414

1515
// Scene Switching
1616
func (s *SceneManager[T]) SwitchTo(scene Scene[T]) {
17+
if prevTransition, ok := s.current.(SceneTransition[T]); ok {
18+
// previous transition is still running, end it first
19+
prevTransition.End()
20+
}
1721
if c, ok := s.current.(Scene[T]); ok {
1822
scene.Load(c.Unload(), s)
1923
s.current = scene
2024
}
2125
}
2226

2327
func (s *SceneManager[T]) SwitchWithTransition(scene Scene[T], transition SceneTransition[T]) {
28+
if prevTransition, ok := s.current.(SceneTransition[T]); ok {
29+
// previous transition is still running, end it to start the next transition
30+
prevTransition.End()
31+
}
2432
sc := s.current.(Scene[T])
2533
transition.Start(sc, scene, s)
2634
if c, ok := sc.(TransitionAwareScene[T]); ok {
@@ -31,11 +39,11 @@ func (s *SceneManager[T]) SwitchWithTransition(scene Scene[T], transition SceneT
3139
s.current = transition
3240
}
3341

34-
func (s *SceneManager[T]) ReturnFromTransition(scene, orgin Scene[T]) {
42+
func (s *SceneManager[T]) ReturnFromTransition(scene, origin Scene[T]) {
3543
if c, ok := scene.(TransitionAwareScene[T]); ok {
36-
c.PostTransition(orgin.Unload(), orgin)
44+
c.PostTransition(origin.Unload(), origin)
3745
} else {
38-
scene.Load(orgin.Unload(), s)
46+
scene.Load(origin.Unload(), s)
3947
}
4048
s.current = scene
4149
}

transition.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func (t *BaseTransition[T]) Layout(outsideWidth, outsideHeight int) (int, int) {
5050

5151
// Ends transition to the next scene
5252
func (t *BaseTransition[T]) End() {
53-
t.sm.ReturnFromTransition(t.toScene.(Scene[T]), t.fromScene.(Scene[T]))
53+
t.sm.ReturnFromTransition(t.toScene, t.fromScene)
5454
}
5555

5656
type FadeTransition[T any] struct {

transition_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,35 @@ func TestBaseTransition_Awareness(t *testing.T) {
104104
assert.True(t, to.postTransitionCalled)
105105
}
106106

107+
func TestBaseTransition_SwitchCanceling(t *testing.T) {
108+
from := &MockScene{}
109+
to := &MockScene{}
110+
trans := &baseTransitionImplementation{}
111+
sm := NewSceneManager[int](from, 0)
112+
sm.SwitchWithTransition(to, trans)
113+
114+
// Assert transition is running
115+
assert.Equal(t, trans, sm.current)
116+
117+
sm.SwitchTo(to)
118+
assert.Equal(t, to, sm.current)
119+
}
120+
121+
func TestBaseTransition_TransitionCanceling(t *testing.T) {
122+
from := &MockScene{}
123+
to := &MockScene{}
124+
transA := &baseTransitionImplementation{}
125+
transB := &baseTransitionImplementation{}
126+
sm := NewSceneManager[int](from, 0)
127+
sm.SwitchWithTransition(to, transA)
128+
129+
// Assert transition is running
130+
assert.Equal(t, transA, sm.current)
131+
132+
sm.SwitchWithTransition(to, transB)
133+
assert.Equal(t, transB, sm.current)
134+
}
135+
107136
func TestFadeTransition_UpdateOncePerFrame(t *testing.T) {
108137
var value float32 = .6
109138
from := &MockScene{}

0 commit comments

Comments
 (0)