Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/tutorius/core.py
diff options
context:
space:
mode:
Diffstat (limited to 'tutorius/core.py')
-rw-r--r--tutorius/core.py177
1 files changed, 106 insertions, 71 deletions
diff --git a/tutorius/core.py b/tutorius/core.py
index be2b0ba..80e1b4f 100644
--- a/tutorius/core.py
+++ b/tutorius/core.py
@@ -24,8 +24,9 @@ This module contains the core classes for tutorius
import logging
import os
-from sugar.tutorius.TProbe import ProbeManager
-from sugar.tutorius import addon
+from .TProbe import ProbeManager
+from .dbustools import save_args
+from . import addon
logger = logging.getLogger("tutorius")
@@ -33,6 +34,9 @@ class Tutorial (object):
"""
Tutorial Class, used to run through the FSM.
"""
+ #Properties
+ probeManager = property(lambda self: self._probeMgr)
+ activityId = property(lambda self: self._activity_id)
def __init__(self, name, fsm, filename=None):
"""
@@ -52,9 +56,6 @@ class Tutorial (object):
self._activity_id = None
#Rest of initialisation happens when attached
- probeManager = property(lambda self: self._probeMgr)
- activityId = property(lambda self: self._activity_id)
-
def attach(self, activity_id):
"""
Attach to a running activity
@@ -78,7 +79,6 @@ class Tutorial (object):
# Uninstall the whole FSM
self.state_machine.teardown()
- #FIXME (Old) There should be some amount of resetting done here...
if not self._activity_id is None:
self._probeMgr.detach(self._activity_id)
self._activity_id = None
@@ -91,18 +91,6 @@ class Tutorial (object):
self.state_machine.set_state(name)
-
- # Currently unused -- equivalent function is in each state
- def _eventfilter_state_done(self, eventfilter):
- """
- Callback handler for eventfilter to notify
- when we must go to the next state.
- """
- #XXX Tests should be run here normally
-
- #Swith to the next state pointed by the eventfilter
- self.set_state(eventfilter.get_next_state())
-
def _prepare_activity(self):
"""
Prepare the activity for the tutorial by loading the saved state and
@@ -116,7 +104,9 @@ class Tutorial (object):
self.activity_init_state_filename
readfile = addon.create("ReadFile", filename=filename)
if readfile:
- self._probeMgr.install(self._activity_id, readfile)
+ self._probeMgr.install(readfile)
+ #Uninstall now while we have the reference handy
+ self._probeMgr.uninstall(readfile)
class State(object):
"""
@@ -143,10 +133,9 @@ class State(object):
self._actions = action_list or []
- # Unused for now
- #self.tests = []
+ self._transitions= dict(event_filter_list or [])
- self._event_filters = event_filter_list or []
+ self._installedEvents = set()
self.tutorial = tutorial
@@ -170,8 +159,8 @@ class State(object):
Install the state itself, by first registering the event filters
and then triggering the actions.
"""
- for eventfilter in self._event_filters:
- self.tutorial.probeManager.subscribe(eventfilter, self._event_filter_state_done_cb )
+ for (event, next_state) in self._transitions.items():
+ self._installedEvents.add(self.tutorial.probeManager.subscribe(event, save_args(self._event_filter_state_done_cb, next_state )))
for action in self._actions:
self.tutorial.probeManager.install(action)
@@ -183,38 +172,37 @@ class State(object):
removing dialogs that were displayed, removing highlights, etc...
"""
# Remove the handlers for the all of the state's event filters
- for event_filter in self._event_filters:
- self.tutorial.probeManager.unsubscribe(event_filter, self._event_filter_state_done_cb )
+ while len(self._installedEvents) > 0:
+ self.tutorial.probeManager.unsubscribe(self._installedEvents.pop())
# Undo all the actions related to this state
for action in self._actions:
self.tutorial.probeManager.uninstall(action)
- def _event_filter_state_done_cb(self, event_filter):
+ def _event_filter_state_done_cb(self, next_state, event):
"""
Callback for event filters. This function needs to inform the
tutorial that the state is over and tell it what is the next state.
- @param event_filter The event filter that was called
+ @param next_state The next state for the transition
+ @param event The event that occured
"""
# Run the tests here, if need be
# Warn the higher level that we wish to change state
- self.tutorial.set_state(event_filter.get_next_state())
+ self.tutorial.set_state(next_state)
# Model manipulation
# These functions are used to simplify the creation of states
def add_action(self, new_action):
"""
- Adds an action to the state (only if it wasn't added before)
+ Adds an action to the state
@param new_action The new action to execute when in this state
@return True if added, False otherwise
"""
- if new_action not in self._actions:
- self._actions.append(new_action)
- return True
- return False
+ self._actions.append(new_action)
+ return True
# remove_action - We did not define names for the action, hence they're
# pretty hard to remove on a precise basis
@@ -230,19 +218,21 @@ class State(object):
Removes all the action associated with this state. A cleared state will
not do anything when entered or exited.
"""
+ #FIXME What if the action is currently installed?
self._actions = []
- def add_event_filter(self, event_filter):
+ def add_event_filter(self, event, next_state):
"""
Adds an event filter that will cause a transition from this state.
The same event filter may not be added twice.
- @param event_filter The new event filter that will trigger a transition
+ @param event The event that will trigger a transition
+ @param next_state The state to which the transition will lead
@return True if added, False otherwise
"""
- if event_filter not in self._event_filters:
- self._event_filters.append(event_filter)
+ if event not in self._transitions.keys():
+ self._transitions[event]=next_state
return True
return False
@@ -250,7 +240,7 @@ class State(object):
"""
@return The list of event filters associated with this state.
"""
- return self._event_filters
+ return self._transitions.items()
def clear_event_filters(self):
"""
@@ -258,12 +248,19 @@ class State(object):
was just cleared will become a sink and will be the end of the
tutorial.
"""
- self._event_filters = []
+ self._transitions = {}
- def is_identical(self, otherState):
+ def __eq__(self, otherState):
"""
- Compares two states and tells whether they contain the same states and
+ Compares two states and tells whether they contain the same states with the
+ same actions and event filters.
+ @param otherState The other State that we wish to match
+ @returns True if every action in this state has a matching action in the
+ other state with the same properties and values AND if every
+ event filters in this state has a matching filter in the
+ other state having the same properties and values AND if both
+ states have the same name.
` """
if not isinstance(otherState, State):
return False
@@ -273,27 +270,28 @@ class State(object):
# Do they have the same actions?
if len(self._actions) != len(otherState._actions):
return False
+
+ if len(self._transitions) != len(otherState._transitions):
+ return False
+
for act in self._actions:
found = False
+ # For each action in the other state, try to match it with this one.
for otherAct in otherState._actions:
- if act.is_identical(otherAct):
+ if act == otherAct:
found = True
break
if found == False:
+ # If we arrive here, then we could not find an action with the
+ # same values in the other state. We know they're not identical
return False
# Do they have the same event filters?
- if len(self._actions) != len(otherState._actions):
+ if self._transitions != otherState._transitions:
return False
- for event in self._event_filters:
- found = False
- for otherEvent in otherState._event_filters:
- if event.is_identical(otherEvent):
- found = True
- break
- if found == False:
- return False
+ # If nothing failed up to now, then every actions and every filters can
+ # be found in the other state
return True
class FiniteStateMachine(State):
@@ -507,9 +505,9 @@ class FiniteStateMachine(State):
#TODO : Move this code inside the State itself - we're breaking
# encap :P
- for event_filter in st._event_filters:
- if event_filter.get_next_state() == state_name:
- st._event_filters.remove(event_filter)
+ for event in st._transitions:
+ if st._transitions[event] == state_name:
+ del st._transitions[event]
# Remove the state from the dictionary
del self._states[state_name]
@@ -527,8 +525,8 @@ class FiniteStateMachine(State):
next_states = set()
- for event_filter in state._event_filters:
- next_states.add(event_filter.get_next_state())
+ for event, state in state._transitions.items():
+ next_states.add(state)
return tuple(next_states)
@@ -550,9 +548,9 @@ class FiniteStateMachine(State):
states = []
# Walk through the list of states
for st in self._states.itervalues():
- for event_filter in st._event_filters:
- if event_filter.get_next_state() == state_name:
- states.append(event_filter.get_next_state())
+ for event, state in st._transitions.items():
+ if state == state_name:
+ states.append(state)
continue
return tuple(states)
@@ -564,42 +562,79 @@ class FiniteStateMachine(State):
out_string += st.name + ", "
return out_string
- def is_identical(self, otherFSM):
+ def __eq__(self, otherFSM):
"""
Compares the elements of two FSM to ensure and returns true if they have the
same set of states, containing the same actions and the same event filters.
- @returns True if the two FSMs have the same content false otherwise
+ @returns True if the two FSMs have the same content, False otherwise
"""
if not isinstance(otherFSM, FiniteStateMachine):
return False
+ # Make sure they share the same name
if not (self.name == otherFSM.name) or \
not (self.start_state_name == otherFSM.start_state_name):
return False
-
+
+ # Ensure they have the same number of FSM-level actions
if len(self._actions) != len(otherFSM._actions):
return False
+
# Test that we have all the same FSM level actions
for act in self._actions:
found = False
+ # For every action in the other FSM, try to match it with the
+ # current one.
for otherAct in otherFSM._actions:
- if act.is_identical(otherAct):
+ if act == otherAct:
found = True
break
if found == False:
return False
+ # Make sure we have the same number of states in both FSMs
if len(self._states) != len(otherFSM._states):
return False
- for state in self._states.itervalues():
- found = False
- for otherState in otherFSM._states.itervalues():
- if state.is_identical(otherState):
- found = True
- break
- if found == False:
+ # For each state, try to find a corresponding state in the other FSM
+ for state_name in self._states.keys():
+ state = self._states[state_name]
+ other_state = None
+ try:
+ # Attempt to use this key in the other FSM. If it's not present
+ # the dictionary will throw an exception and we'll know we have
+ # at least one different state in the other FSM
+ other_state = otherFSM._states[state_name]
+ except:
+ return False
+ # If two states with the same name exist, then we want to make sure
+ # they are also identical
+ if not state == other_state:
+ return False
+
+ # If we made it here, then all the states in this FSM could be matched to an
+ # identical state in the other FSM.
+ return True
+ if len(self._states) != len(otherFSM._states):
+ return False
+
+ # For each state, try to find a corresponding state in the other FSM
+ for state_name in self._states.keys():
+ state = self._states[state_name]
+ other_state = None
+ try:
+ # Attempt to use this key in the other FSM. If it's not present
+ # the dictionary will throw an exception and we'll know we have
+ # at least one different state in the other FSM
+ other_state = otherFSM._states[state_name]
+ except:
+ return False
+ # If two states with the same name exist, then we want to make sure
+ # they are also identical
+ if not state == other_state:
return False
+ # If we made it here, then all the states in this FSM could be matched to an
+ # identical state in the other FSM.
return True