Skip to content

Commit

Permalink
Merge pull request #8 from textnow/add-base-state
Browse files Browse the repository at this point in the history
Refactored State into InternalState and ExternalState
  • Loading branch information
Karn authored Jan 20, 2020
2 parents af1534b + 7b992ea commit 69341d4
Show file tree
Hide file tree
Showing 20 changed files with 272 additions and 468 deletions.
61 changes: 61 additions & 0 deletions base_state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package hsm

import (
"fmt"
"go.uber.org/zap"
)

// BaseState implements the InternalState interface
type BaseState struct {
name string
parentState State
logger *zap.Logger
entered bool
exited bool
}

// NewBaseState constructor
func NewBaseState(name string, parentState State, logger *zap.Logger) *BaseState {
return &BaseState{
name: name,
parentState: parentState,
logger: logger,
}
}

// Name getter
func (b *BaseState) Name() string {
return b.name
}

// ParentState getter
func (b *BaseState) ParentState() State {
return b.parentState
}

// Entered getter
func (b *BaseState) Entered() bool {
return b.entered
}

// Exited getter
func (b *BaseState) Exited() bool {
return b.exited
}

// Logger getter
func (b *BaseState) Logger() *zap.Logger {
return b.logger
}

// VerifyNotEntered makes sure that this state is only entered once
func (b *BaseState) VerifyNotEntered() {
Precondition(b.logger, !b.entered, fmt.Sprintf("State %s has already been entered", b.name))
b.entered = true
}

// VerifyNotExited make sure that this state is only exited once
func (b *BaseState) VerifyNotExited() {
Precondition(b.logger, !b.exited, fmt.Sprintf("State %s has already been exited", b.name))
b.exited = true
}
36 changes: 36 additions & 0 deletions base_state_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package hsm

import (
"github.com/stretchr/testify/assert"
"go.uber.org/zap/zaptest"
"testing"
)

func TestNewBaseState(t *testing.T) {
logger := zaptest.NewLogger(t)
baseState := NewBaseState("baseStateName", nil, logger)

assert.Equal(t, "baseStateName", baseState.Name())
assert.Nil(t, baseState.ParentState())
assert.False(t, baseState.Entered())
assert.False(t, baseState.Exited())
assert.Equal(t, logger, baseState.Logger())
}

func TestBaseState_VerifyNotEntered(t *testing.T) {
logger := zaptest.NewLogger(t)
baseState := NewBaseState("baseStateName", nil, logger)

assert.False(t, baseState.Entered())
baseState.VerifyNotEntered()
assert.True(t, baseState.Entered())
}

func TestBaseState_VerifyNotExited(t *testing.T) {
logger := zaptest.NewLogger(t)
baseState := NewBaseState("baseStateName", nil, logger)

assert.False(t, baseState.Exited())
baseState.VerifyNotExited()
assert.True(t, baseState.Exited())
}
46 changes: 9 additions & 37 deletions example/samek/states/state_s0.go
Original file line number Diff line number Diff line change
@@ -1,70 +1,42 @@
package states

import (
"fmt"
"github.com/Enflick/gohsm"
"go.uber.org/zap"
)

// S0State represents State S0
type S0State struct {
logger *zap.Logger
entered bool
exited bool
hsm.BaseState
}

// NewS0State constructor
func NewS0State(logger *zap.Logger) *S0State {
return &S0State{
logger: logger,
BaseState: *hsm.NewBaseState("S0", nil, logger),
}
}

// Name returns the state's name
func (s *S0State) Name() string {
return "S0"
}

// OnEnter enters this state
func (s *S0State) OnEnter(event hsm.Event) hsm.State {
hsm.Precondition(s.logger, !s.entered, fmt.Sprintf("State %s has already been entered", s.Name()))
s.logger.Debug("->S0;")
s.entered = true

stateS1 := NewS1State(s.logger, s)

return stateS1.OnEnter(event)
s.VerifyNotEntered()
s.Logger().Debug("->S0;")
return NewS1State(s.Logger(), s).OnEnter(event)
}

// OnExit enters this state
func (s *S0State) OnExit(event hsm.Event) hsm.State {
hsm.Precondition(s.logger, !s.exited, fmt.Sprintf("State %s has already been exited", s.Name()))
s.logger.Debug("<-S0;")
s.exited = true
s.VerifyNotExited()
s.Logger().Debug("<-S0;")
return s.ParentState()
}

// EventHandler returns a transition associated with the event or NilTransition if the event is not handled
func (s *S0State) EventHandler(event hsm.Event) hsm.Transition {
switch event.ID() {
case ee.ID():
return hsm.NewExternalTransition(event, NewS2State(s.logger, s), hsm.NopAction)
return hsm.NewExternalTransition(event, NewS2State(s.Logger(), s), hsm.NopAction)
default:
return hsm.NilTransition
return nil
}
}

// ParentState returns the parentState or NilState
func (s *S0State) ParentState() hsm.State {
return hsm.NilState
}

// Entered returns true if this state have been entered
func (s *S0State) Entered() bool {
return s.entered
}

// Exited returns true if this state have been exited
func (s *S0State) Exited() bool {
return s.exited
}
57 changes: 14 additions & 43 deletions example/samek/states/state_s1.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,77 +8,48 @@ import (

// S1State represents State S1
type S1State struct {
logger *zap.Logger
parentState *S0State
entered bool
exited bool
hsm.BaseState
}

// NewS1State constructor
func NewS1State(logger *zap.Logger, parentState *S0State) *S1State {
func NewS1State(logger *zap.Logger, parentState hsm.State) *S1State {
hsm.Precondition(logger, parentState != nil, fmt.Sprintf("NewS1State: parentState cannot be nil"))

state := &S1State{
logger: logger,
parentState: parentState,
BaseState: *hsm.NewBaseState("S1", parentState, logger),
}

return state
}

// Name returns the state's name
func (s *S1State) Name() string {
return "S1"
}

// OnEnter enters this state
func (s *S1State) OnEnter(event hsm.Event) hsm.State {
hsm.Precondition(s.logger, !s.entered, fmt.Sprintf("State %s has already been entered", s.Name()))
s.logger.Debug("->S1;")
s.entered = true

stateS1 := NewS11State(s.logger, s)

return stateS1.OnEnter(event)
s.VerifyNotEntered()
s.Logger().Debug("->S1;")
return NewS11State(s.Logger(), s).OnEnter(event)
}

// OnExit enters this state
func (s *S1State) OnExit(event hsm.Event) hsm.State {
hsm.Precondition(s.logger, !s.exited, fmt.Sprintf("State %s has already been exited", s.Name()))
s.logger.Debug("<-S1;")
s.exited = true
s.VerifyNotExited()
s.Logger().Debug("<-S1;")
return s.ParentState()
}

// EventHandler returns a transition associated with the event or NilTransition if the event is not handled
func (s *S1State) EventHandler(event hsm.Event) hsm.Transition {
switch event.ID() {
case ea.ID():
return hsm.NewExternalTransition(event, NewS1State(s.logger, s.parentState), hsm.NopAction)
return hsm.NewExternalTransition(event, NewS1State(s.Logger(), s.ParentState()), hsm.NopAction)
case eb.ID():
return hsm.NewExternalTransition(event, NewS11State(s.logger, s), hsm.NopAction)
return hsm.NewExternalTransition(event, NewS11State(s.Logger(), s), hsm.NopAction)
case ec.ID():
return hsm.NewExternalTransition(event, NewS2State(s.logger, s.parentState), hsm.NopAction)
return hsm.NewExternalTransition(event, NewS2State(s.Logger(), s.ParentState()), hsm.NopAction)
case ed.ID():
return hsm.NewExternalTransition(event, NewS0State(s.logger), hsm.NopAction)
return hsm.NewExternalTransition(event, NewS0State(s.Logger()), hsm.NopAction)
case ef.ID():
return hsm.NewExternalTransition(event, NewS2State(s.logger, s.parentState), hsm.NopAction)
return hsm.NewExternalTransition(event, NewS2State(s.Logger(), s.ParentState()), hsm.NopAction)
default:
return hsm.NilTransition
return nil
}
}

// ParentState returns the parentState or NilState
func (s *S1State) ParentState() hsm.State {
return s.parentState
}

// Entered returns true if this state have been entered
func (s *S1State) Entered() bool {
return s.entered
}

// Exited returns true if this state have been exited
func (s *S1State) Exited() bool {
return s.exited
}
42 changes: 8 additions & 34 deletions example/samek/states/state_s11.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,66 +8,40 @@ import (

// S11State represents State S11
type S11State struct {
logger *zap.Logger
parentState *S1State
entered bool
exited bool
hsm.BaseState
}

// NewS11State constructor
func NewS11State(logger *zap.Logger, parentState *S1State) *S11State {
hsm.Precondition(logger, parentState != nil, fmt.Sprintf("NewS11State: parentState cannot be nil"))

state := &S11State{
logger: logger,
parentState: parentState,
BaseState: *hsm.NewBaseState("S11", parentState, logger),
}

return state
}

// Name returns the state's name
func (s *S11State) Name() string {
return "S11"
}

// OnEnter enters this state
func (s *S11State) OnEnter(event hsm.Event) hsm.State {
hsm.Precondition(s.logger, !s.entered, fmt.Sprintf("State %s has already been entered", s.Name()))
s.logger.Debug("->S11;")
s.entered = true
s.VerifyNotEntered()
s.Logger().Debug("->S11;")
return s
}

// OnExit enters this state
func (s *S11State) OnExit(event hsm.Event) hsm.State {
hsm.Precondition(s.logger, !s.exited, fmt.Sprintf("State %s has already been exited", s.Name()))
s.logger.Debug("<-S11;")
s.exited = true
s.VerifyNotExited()
s.Logger().Debug("<-S11;")
return s.ParentState()
}

// EventHandler returns a transition associated with the event or NilTransition if the event is not handled
func (s *S11State) EventHandler(event hsm.Event) hsm.Transition {
switch event.ID() {
case eg.ID():
return hsm.NewExternalTransition(event, NewS2State(s.logger, s.parentState.parentState), hsm.NopAction)
return hsm.NewExternalTransition(event, NewS2State(s.Logger(), s.ParentState().ParentState()), hsm.NopAction)
default:
return hsm.NilTransition
return nil
}
}

// ParentState returns the parentState or NilState
func (s *S11State) ParentState() hsm.State {
return s.parentState
}

// Entered returns true if this state have been entered
func (s *S11State) Entered() bool {
return s.entered
}

// Exited returns true if this state have been exited
func (s *S11State) Exited() bool {
return s.exited
}
Loading

0 comments on commit 69341d4

Please # to comment.