Skip to content

Commit

Permalink
Merge pull request #8 from harbdog/fix-transition-panic
Browse files Browse the repository at this point in the history
Fix transition panic while currently in transition
  • Loading branch information
joelschutz authored Jun 2, 2024
2 parents d54b85e + eef3250 commit 577c465
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 16 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 8 additions & 3 deletions director.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ 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, end it to process trigger
prevTransition.End()
}

for _, directive := range d.RuleSet[d.current.(Scene[T])] {
if directive.Trigger == trigger {
if directive.Transition != nil {
Expand All @@ -50,11 +55,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
}
45 changes: 45 additions & 0 deletions director_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
54 changes: 45 additions & 9 deletions examples/director/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const (
type BaseScene struct {
bounds image.Rectangle
count State
active bool
sm *stagehand.SceneDirector[State]
}

Expand All @@ -36,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
}

Expand All @@ -51,15 +54,15 @@ func (s *FirstScene) Update() error {
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
s.count++
}
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) {
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) && s.active {
s.sm.ProcessTrigger(Trigger)
}
return nil
}

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\nActive? %v", s.count, s.bounds.Max, s.active), s.bounds.Dx()/2, s.bounds.Dy()/2)
}

type SecondScene struct {
Expand All @@ -70,15 +73,34 @@ func (s *SecondScene) Update() error {
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
s.count--
}
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) {
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) && s.active {
s.sm.ProcessTrigger(Trigger)
}
return nil
}

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\nActive? %v", s.count, s.bounds.Max, s.active), 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.active {
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\nActive? %v", s.count, s.bounds.Max, s.active), s.bounds.Dx()/2, s.bounds.Dy()/2)
}

func main() {
Expand All @@ -90,16 +112,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]{
s2: {
stagehand.Directive[State]{
Dest: s3,
Trigger: Trigger,
Transition: trans2,
},
},
s3: {
stagehand.Directive[State]{
Dest: s1,
Trigger: Trigger,
Transition: trans,
Transition: trans3,
},
},
}
Expand Down
14 changes: 11 additions & 3 deletions manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,21 @@ 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
}
}

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 {
Expand All @@ -31,11 +39,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
}
Expand Down
2 changes: 1 addition & 1 deletion transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
29 changes: 29 additions & 0 deletions transition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down

0 comments on commit 577c465

Please sign in to comment.