Refactor FSMBase to subclass transitions.Machine

This commit is contained in:
Kristóf Tóth 2018-07-04 15:48:16 +02:00
parent 91c257554f
commit 7a92d88b73
2 changed files with 36 additions and 40 deletions

View File

@ -1,8 +1,6 @@
# Copyright (C) 2018 Avatao.com Innovative Learning Kft. # Copyright (C) 2018 Avatao.com Innovative Learning Kft.
# All Rights Reserved. See LICENSE file for details. # All Rights Reserved. See LICENSE file for details.
from collections import defaultdict
from tfw import BroadcastingEventHandler from tfw import BroadcastingEventHandler
from tfw.config.logs import logging from tfw.config.logs import logging
@ -13,7 +11,6 @@ class FSMManagingEventHandler(BroadcastingEventHandler):
def __init__(self, key, fsm_type): def __init__(self, key, fsm_type):
super().__init__(key) super().__init__(key)
self.fsm = fsm_type() self.fsm = fsm_type()
self.fsm_manager = FSMManager(self.fsm)
self._fsm_updater = FSMUpdater(self.fsm) self._fsm_updater = FSMUpdater(self.fsm)
self.command_handlers = { self.command_handlers = {
@ -30,7 +27,7 @@ class FSMManagingEventHandler(BroadcastingEventHandler):
LOG.error('IGNORING MESSAGE: Invalid message received: %s', message) LOG.error('IGNORING MESSAGE: Invalid message received: %s', message)
def handle_trigger(self, data): def handle_trigger(self, data):
self.fsm_manager.trigger(data['value']) self.fsm.step(data['value'])
return self.with_fsm_update(data) return self.with_fsm_update(data)
def with_fsm_update(self, data): def with_fsm_update(self, data):
@ -43,35 +40,6 @@ class FSMManagingEventHandler(BroadcastingEventHandler):
return self.with_fsm_update(data) return self.with_fsm_update(data)
class FSMManager:
def __init__(self, fsm):
self.fsm = fsm
self.trigger_predicates = defaultdict(list)
def trigger(self, trigger):
predicate_results = [
predicate()
for predicate in self.trigger_predicates[trigger]
]
# TODO: think about what could we do when this prevents triggering
if all(predicate_results):
try:
self.fsm.trigger(trigger)
except AttributeError:
LOG.debug('FSM failed to execute nonexistent trigger: "%s"', trigger)
def subscribe_predicate(self, trigger, *predicates):
self.trigger_predicates[trigger].extend(predicates)
def unsubscribe_predicate(self, trigger, *predicates):
self.trigger_predicates[trigger] = [
predicate
for predicate in self.trigger_predicates[trigger]
not in predicates
]
class FSMUpdater: class FSMUpdater:
def __init__(self, fsm): def __init__(self, fsm):
self.fsm = fsm self.fsm = fsm
@ -80,7 +48,7 @@ class FSMUpdater:
state = self.fsm.state state = self.fsm.state
valid_transitions = [ valid_transitions = [
{'trigger': trigger} {'trigger': trigger}
for trigger in self.fsm.machine.get_triggers(self.fsm.state) for trigger in self.fsm.get_triggers(self.fsm.state)
] ]
return { return {
'current_state': state, 'current_state': state,

View File

@ -1,14 +1,17 @@
# Copyright (C) 2018 Avatao.com Innovative Learning Kft. # Copyright (C) 2018 Avatao.com Innovative Learning Kft.
# All Rights Reserved. See LICENSE file for details. # All Rights Reserved. See LICENSE file for details.
from typing import List from collections import defaultdict
from transitions import Machine from transitions import Machine
from tfw.mixins import CallbackMixin from tfw.mixins import CallbackMixin
from tfw.config.logs import logging
LOG = logging.getLogger(__name__)
class FSMBase(CallbackMixin): class FSMBase(Machine, CallbackMixin):
""" """
A general FSM base class you can inherit from to track user progress. A general FSM base class you can inherit from to track user progress.
See linear_fsm.py for an example use-case. See linear_fsm.py for an example use-case.
@ -18,10 +21,12 @@ class FSMBase(CallbackMixin):
""" """
states, transitions = [], [] states, transitions = [], []
def __init__(self, initial: str = None, accepted_states: List[str] = None): def __init__(self, initial=None, accepted_states=None):
self.accepted_states = accepted_states or [self.states[-1]] self.accepted_states = accepted_states or [self.states[-1]]
self.machine = Machine( self.trigger_predicates = defaultdict(list)
model=self,
Machine.__init__(
self,
states=self.states, states=self.states,
transitions=self.transitions, transitions=self.transitions,
initial=initial or self.states[0], initial=initial or self.states[0],
@ -35,3 +40,26 @@ class FSMBase(CallbackMixin):
def is_solved(self): def is_solved(self):
return self.state in self.accepted_states # pylint: disable=no-member return self.state in self.accepted_states # pylint: disable=no-member
def subscribe_predicate(self, trigger, *predicates):
self.trigger_predicates[trigger].extend(predicates)
def unsubscribe_predicate(self, trigger, *predicates):
self.trigger_predicates[trigger] = [
predicate
for predicate in self.trigger_predicates[trigger]
not in predicates
]
def step(self, trigger):
predicate_results = [
predicate()
for predicate in self.trigger_predicates[trigger]
]
# TODO: think about what could we do when this prevents triggering
if all(predicate_results):
try:
self.trigger(trigger)
except AttributeError:
LOG.debug('FSM failed to execute nonexistent trigger: "%s"', trigger)