From 40e1c67f88d07b6f26fc4bebf297db454b534baa Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 8 Oct 2016 19:39:52 +0200 Subject: [PATCH] Change how cmdset options are merged by priority - this is now a straight priority order, where the option from the higher prio goes. Also add unit tests for cmdset mergers. --- evennia/commands/cmdhandler.py | 4 +- evennia/commands/cmdset.py | 82 +++++++++-------- evennia/commands/tests.py | 164 +++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+), 38 deletions(-) create mode 100644 evennia/commands/tests.py diff --git a/evennia/commands/cmdhandler.py b/evennia/commands/cmdhandler.py index 6d1e92227a..fbe2a57721 100644 --- a/evennia/commands/cmdhandler.py +++ b/evennia/commands/cmdhandler.py @@ -329,7 +329,7 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype): prio = cmdset.priority if prio in tempmergers: # merge same-prio cmdset together separately - tempmergers[prio] = yield cmdset + tempmergers[prio] + tempmergers[prio] = yield tempmergers[prio] + cmdset else: tempmergers[prio] = cmdset @@ -339,7 +339,7 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype): # Merge all command sets into one, beginning with the lowest-prio one cmdset = cmdsets[0] for merging_cmdset in cmdsets[1:]: - cmdset = yield merging_cmdset + cmdset + cmdset = yield cmdset + merging_cmdset # store the full sets for diagnosis cmdset.merged_from = cmdsets # cache diff --git a/evennia/commands/cmdset.py b/evennia/commands/cmdset.py index a36eca8624..02f26d23e8 100644 --- a/evennia/commands/cmdset.py +++ b/evennia/commands/cmdset.py @@ -351,52 +351,61 @@ class CmdSet(with_metaclass(_CmdSetMeta, object)): self._contains_cache[othercmd] = ret return ret - def __add__(self, cmdset_b): + def __add__(self, cmdset_a): """ - Merge this cmdset (A) with another cmdset (B) using the + operator, + Merge this cmdset (B) with another cmdset (A) using the + operator, - C = A + B + C = B + A Here, we (by convention) say that 'A is merged onto B to form C'. The actual merge operation used in the 'addition' depends on which priorities A and B have. The one of the two with the highest priority will apply and give its properties to C. In - the case of a tie, A takes priority and replaces the + the case of a tie, A takes priority and replaces the same-named commands in B unless A has the 'duplicate' variable set (which means both sets' commands are kept). """ # It's okay to merge with None - if not cmdset_b: + if not cmdset_a: return self - sys_commands_a = self.get_system_cmds() - sys_commands_b = cmdset_b.get_system_cmds() + sys_commands_a = cmdset_a.get_system_cmds() + sys_commands_b = self.get_system_cmds() - if self.priority >= cmdset_b.priority: - # A higher or equal priority than B + if self.priority <= cmdset_a.priority: + # A higher or equal priority to B # preserve system __commands sys_commands = sys_commands_a + [cmd for cmd in sys_commands_b if cmd not in sys_commands_a] - mergetype = self.key_mergetypes.get(cmdset_b.key, self.mergetype) + mergetype = cmdset_a.key_mergetypes.get(self.key, cmdset_a.mergetype) if mergetype == "Intersect": - cmdset_c = self._intersect(self, cmdset_b) + cmdset_c = self._intersect(cmdset_a, self) elif mergetype == "Replace": - cmdset_c = self._replace(self, cmdset_b) + cmdset_c = self._replace(cmdset_a, self) elif mergetype == "Remove": - cmdset_c = self._remove(self, cmdset_b) - else: # Union - cmdset_c = self._union(self, cmdset_b) - # update or pass-through - cmdset_c.no_channels = cmdset_b.no_channels if self.no_channels is None else self.no_channels - cmdset_c.no_exits = cmdset_b.no_exits if self.no_exits is None else self.no_exits - cmdset_c.no_objs = cmdset_b.no_objs if self.no_objs is None else self.no_objs - cmdset_c.duplicates = cmdset_b.duplicates if self.duplicates is None else self.duplicates + cmdset_c = self._remove(cmdset_a, self) + else: # Union + cmdset_c = self._union(cmdset_a, self) + + if self.priority == cmdset_a.priority: + # same prio - pass through if changed + cmdset_c.no_channels = self.no_channels if cmdset_a.no_channels is None else cmdset_a.no_channels + cmdset_c.no_exits = self.no_exits if cmdset_a.no_exits is None else cmdset_a.no_exits + cmdset_c.no_objs = self.no_objs if cmdset_a.no_objs is None else cmdset_a.no_objs + cmdset_c.duplicates = self.duplicates if cmdset_a.duplicates is None else cmdset_a.duplicates + else: + # pass through the options of the higher prio set + cmdset_c.no_channels = cmdset_a.no_channels + cmdset_c.no_exits = cmdset_a.no_exits + cmdset_c.no_objs = cmdset_a.no_objs + cmdset_c.duplicates = cmdset_a.duplicates + if self.key.startswith("_"): # don't rename new output if the merge set's name starts with _ - cmdset_c.key = cmdset_b.key + cmdset_c.key = cmdset_a.key else: # B higher priority than A @@ -405,32 +414,33 @@ class CmdSet(with_metaclass(_CmdSetMeta, object)): sys_commands = sys_commands_b + [cmd for cmd in sys_commands_a if cmd not in sys_commands_b] - mergetype = cmdset_b.key_mergetypes.get(self.key, cmdset_b.mergetype) + mergetype = self.key_mergetypes.get(cmdset_a.key, self.mergetype) if mergetype == "Intersect": - cmdset_c = self._intersect(cmdset_b, self) + cmdset_c = self._intersect(self, cmdset_a) elif mergetype == "Replace": - cmdset_c = self._replace(cmdset_b, self) + cmdset_c = self._replace(self, cmdset_a) elif mergetype == "Remove": - cmdset_c = self._remove(cmdset_b, self) - else: # Union - cmdset_c = self._union(cmdset_b, self) - cmdset_c.no_channels = cmdset_b.no_channels - cmdset_c.no_exits = cmdset_b.no_exits - cmdset_c.no_objs = cmdset_b.no_objs + cmdset_c = self._remove(self, cmdset_a) + else: # Union + cmdset_c = self._union(self, cmdset_a) + + cmdset_c.no_channels = self.no_channels + cmdset_c.no_exits = self.no_exits + cmdset_c.no_objs = self.no_objs + cmdset_c.duplicates = self.duplicates + # update or pass-through - cmdset_c.no_channels = self.no_channels if self.no_channels is None else cmdset_b.no_channels - cmdset_c.no_exits = self.no_exits if self.no_exits is None else cmdset_b.no_exits - cmdset_c.no_objs = self.no_objs if self.no_objs is None else cmdset_b.no_objs - cmdset_c.duplicates = self.duplicates if self.duplicates is None else cmdset_b.duplicates - if cmdset_b.key.startswith("_"): + if self.key.startswith("_"): # don't rename new output if the merge set's name starts with _ - cmdset_c.key = self.key + cmdset_c.key = cmdset_a.self.key # we store actual_mergetype since key_mergetypes # might be different from the main mergetype. # This is used for diagnosis. cmdset_c.actual_mergetype = mergetype + #print "__add__ for %s (prio %i) called with %s (prio %i)." % (self.key, self.priority, cmdset_a.key, cmdset_a.priority) + # return the system commands to the cmdset cmdset_c.add(sys_commands) return cmdset_c diff --git a/evennia/commands/tests.py b/evennia/commands/tests.py new file mode 100644 index 0000000000..95e670cb52 --- /dev/null +++ b/evennia/commands/tests.py @@ -0,0 +1,164 @@ +""" +Unit testing for the Command system itself. + +""" + +from evennia.utils.test_resources import EvenniaTest, SESSIONS +from evennia.commands.cmdset import CmdSet +from evennia.commands.command import Command + + +# Testing-command sets + +class _CmdA(Command): + key = "A" + def __init__(self, cmdset, *args, **kwargs): + super(_CmdA, self).__init__(*args, **kwargs) + self.from_cmdset = cmdset +class _CmdB(Command): + key = "B" + def __init__(self, cmdset, *args, **kwargs): + super(_CmdB, self).__init__(*args, **kwargs) + self.from_cmdset = cmdset +class _CmdC(Command): + key = "C" + def __init__(self, cmdset, *args, **kwargs): + super(_CmdC, self).__init__(*args, **kwargs) + self.from_cmdset = cmdset +class _CmdD(Command): + key = "D" + def __init__(self, cmdset, *args, **kwargs): + super(_CmdD, self).__init__(*args, **kwargs) + self.from_cmdset = cmdset + +class _CmdSetA(CmdSet): + key = "A" + def at_cmdset_creation(self): + self.add(_CmdA("A")) + self.add(_CmdB("A")) + self.add(_CmdC("A")) + self.add(_CmdD("A")) +class _CmdSetB(CmdSet): + key = "B" + def at_cmdset_creation(self): + self.add(_CmdA("B")) + self.add(_CmdB("B")) + self.add(_CmdC("B")) +class _CmdSetC(CmdSet): + key = "C" + def at_cmdset_creation(self): + self.add(_CmdA("C")) + self.add(_CmdB("C")) +class _CmdSetD(CmdSet): + key = "D" + def at_cmdset_creation(self): + self.add(_CmdA("D")) + self.add(_CmdB("D")) + self.add(_CmdC("D")) + self.add(_CmdD("D")) + +# testing Command Sets + +class TestCmdSetMergers(EvenniaTest): + "Test merging of cmdsets" + def setUp(self): + super(TestCmdSetMergers, self).setUp() + self.cmdset_a = _CmdSetA() + self.cmdset_b = _CmdSetB() + self.cmdset_c = _CmdSetC() + self.cmdset_d = _CmdSetD() + + def test_order(self): + "Merge in reverse- and forward orders, same priorities" + a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d + cmdset_f = d + c + b + a # merge in reverse order of priority + self.assertEqual(cmdset_f.priority, 0) + self.assertEqual(cmdset_f.mergetype, "Union") + self.assertEqual(len(cmdset_f.commands), 4) + self.assertTrue(all(True for cmd in cmdset_f.commands if cmd.from_cmdset == "A")) + cmdset_f = a + b + c + d # merge in order of priority + self.assertEqual(cmdset_f.priority, 0) + self.assertEqual(cmdset_f.mergetype, "Union") + self.assertEqual(len(cmdset_f.commands), 4) # duplicates setting from A transfers + self.assertTrue(all(True for cmd in cmdset_f.commands if cmd.from_cmdset == "D")) + + def test_priority_order(self): + "Merge in reverse- and forward order with well-defined prioritities" + a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d + a.priority = 2 + b.priority = 1 + c.priority = 0 + d.priority = -1 + cmdset_f = d + c + b + a # merge in reverse order of priority + self.assertEqual(cmdset_f.priority, 2) + self.assertEqual(cmdset_f.mergetype, "Union") + self.assertEqual(len(cmdset_f.commands), 4) + self.assertTrue(all(True for cmd in cmdset_f.commands if cmd.from_cmdset == "A")) + cmdset_f = a + b + c + d # merge in order of priority + self.assertEqual(cmdset_f.priority, 2) + self.assertEqual(cmdset_f.mergetype, "Union") + self.assertEqual(len(cmdset_f.commands), 4) + self.assertTrue(all(True for cmd in cmdset_f.commands if cmd.from_cmdset == "A")) + + def test_option_transfer(self): + "Test transfer of cmdset options" + a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d + a.no_exits = True + a.no_objs = True + a.no_channels = True + a.duplicates = True + cmdset_f = d + c + b + a # reverse, same-prio + self.assertTrue(cmdset_f.no_exits) + self.assertTrue(cmdset_f.no_objs) + self.assertTrue(cmdset_f.no_channels) + self.assertTrue(cmdset_f.duplicates) + self.assertEqual(len(cmdset_f.commands), 8) + cmdset_f = a + b + c + d # forward, same-prio + self.assertTrue(cmdset_f.no_exits) + self.assertTrue(cmdset_f.no_objs) + self.assertTrue(cmdset_f.no_channels) + self.assertTrue(cmdset_f.duplicates) + self.assertEqual(len(cmdset_f.commands), 4) + a.priority = 2 + b.priority = 1 + c.priority = 0 + d.priority = -1 + cmdset_f = d + c + b + a # reverse, A top priority + self.assertTrue(cmdset_f.no_exits) + self.assertTrue(cmdset_f.no_objs) + self.assertTrue(cmdset_f.no_channels) + self.assertTrue(cmdset_f.duplicates) + self.assertEqual(len(cmdset_f.commands), 4) + cmdset_f = a + b + c + d # forward, A top priority + self.assertTrue(cmdset_f.no_exits) + self.assertTrue(cmdset_f.no_objs) + self.assertTrue(cmdset_f.no_channels) + self.assertTrue(cmdset_f.duplicates) + self.assertEqual(len(cmdset_f.commands), 4) + a.priority = -1 + b.priority = 0 + c.priority = 1 + d.priority = 2 + cmdset_f = d + c + b + a # reverse, A low prio + self.assertFalse(cmdset_f.no_exits) + self.assertFalse(cmdset_f.no_objs) + self.assertFalse(cmdset_f.no_channels) + self.assertFalse(cmdset_f.duplicates) + self.assertEqual(len(cmdset_f.commands), 4) + cmdset_f = a + b + c + d # forward, A low prio + self.assertFalse(cmdset_f.no_exits) + self.assertFalse(cmdset_f.no_objs) + self.assertFalse(cmdset_f.no_channels) + self.assertFalse(cmdset_f.duplicates) + self.assertEqual(len(cmdset_f.commands), 4) + a.priority = 0 + b.priority = 0 + c.priority = 0 + d.priority = 0 + c.duplicates = True + cmdset_f = d + b + c + a # two last mergers duplicates=True + self.assertEqual(len(cmdset_f.commands), 10) + + + +