Pass kwargs from get_stages/dt to staging callable in ondemandhandler
This commit is contained in:
parent
1853b29429
commit
34b5f1133c
5 changed files with 148 additions and 50 deletions
|
|
@ -3,10 +3,10 @@ Helper to handle on-demand requests, allowing a system to change state only when
|
|||
actually needs the information. This is a very efficient way to handle gradual changes, requiring
|
||||
not computer resources until the state is actually needed.
|
||||
|
||||
For example, consider a flowering system, where a seed sprouts, grows and blooms over a certain time.
|
||||
One _could_ implement this with e.g. a Script or a ticker that gradually moves the flower along
|
||||
its stages of growth. But what if that flower is in a remote location, and no one is around to see it?
|
||||
You are then wasting computational resources on something that no one is looking at.
|
||||
For example, consider a flowering system, where a seed sprouts, grows and blooms over a certain
|
||||
time. One _could_ implement this with e.g. a Script or a ticker that gradually moves the flower
|
||||
along its stages of growth. But what if that flower is in a remote location, and no one is around to
|
||||
see it? You are then wasting computational resources on something that no one is looking at.
|
||||
|
||||
The truth is that most of the time, players are not looking at most of the things in the game. They
|
||||
_only_ need to know about which state the flower is in when they are actually looking at it, or
|
||||
|
|
@ -28,8 +28,8 @@ This is the basic principle, using the flowering system as an example.
|
|||
since too long time has passed and the plant has died.
|
||||
|
||||
With a system like this you could have growing plants all over your world and computing usage would
|
||||
only scale by how many players you have exploring your world. The players will not know the difference
|
||||
between this and a system that is always running, but your server will thank you.
|
||||
only scale by how many players you have exploring your world. The players will not know the
|
||||
difference between this and a system that is always running, but your server will thank you.
|
||||
|
||||
There is only one situation where this system is not ideal, and that is when a player should be
|
||||
informed of the state change _even if they perform no action_. That is, even if they are just idling
|
||||
|
|
@ -69,6 +69,7 @@ from evennia.utils.utils import is_iter
|
|||
_RUNTIME = None
|
||||
|
||||
ON_DEMAND_HANDLER = None
|
||||
ONDEMAND_HANDLER_SAVE_NAME = "on_demand_timers"
|
||||
|
||||
|
||||
class OnDemandTask:
|
||||
|
|
@ -76,10 +77,10 @@ class OnDemandTask:
|
|||
Stores information about an on-demand task.
|
||||
|
||||
Default property:
|
||||
- `default_stage_function (callable)`: This is called if no stage function is given in the stages dict.
|
||||
This is meant for changing the task itself (such as restarting it). Actual game code should
|
||||
be handled elsewhere, by checking this task. See the `stagefunc_*` static methods for examples
|
||||
of how to manipulate the task when a stage is reached.
|
||||
- `default_stage_function (callable)`: This is called if no stage function is given in the
|
||||
stages dict. This is meant for changing the task itself (such as restarting it). Actual
|
||||
game code should be handled elsewhere, by checking this task. See the `stagefunc_*` static
|
||||
methods for examples of how to manipulate the task when a stage is reached.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -100,7 +101,7 @@ class OnDemandTask:
|
|||
return _RUNTIME()
|
||||
|
||||
@staticmethod
|
||||
def stagefunc_loop(task):
|
||||
def stagefunc_loop(task, **kwargs):
|
||||
"""
|
||||
Attach this to the last stage to have the task start over from
|
||||
the beginning
|
||||
|
|
@ -131,7 +132,7 @@ class OnDemandTask:
|
|||
task.start_time = now - current_loop_time
|
||||
|
||||
@staticmethod
|
||||
def stagefunc_bounce(task):
|
||||
def stagefunc_bounce(task, **kwargs):
|
||||
"""
|
||||
This endfunc will have the task reverse direction and go through the stages in
|
||||
reverse order. This stage-function must be placed at both 'ends' of the stage sequence
|
||||
|
|
@ -161,7 +162,8 @@ class OnDemandTask:
|
|||
stages = task.stages
|
||||
task.stages = {abs(k - max_dt): v for k, v in sorted(stages.items())}
|
||||
|
||||
# default fallback stage function. This is called if no stage function is given in the stages dict.
|
||||
# default fallback stage function. This is called if no stage function is given in the stages
|
||||
# dict.
|
||||
default_stage_function = None
|
||||
|
||||
def __init__(self, key, category, stages=None, autostart=True):
|
||||
|
|
@ -239,13 +241,14 @@ class OnDemandTask:
|
|||
return False
|
||||
return (self.key, self.category) == (other.key, other.category)
|
||||
|
||||
def check(self, autostart=True):
|
||||
def check(self, autostart=True, **kwargs):
|
||||
"""
|
||||
Check the current stage of the task and return the time-delta to the next stage.
|
||||
|
||||
Args:
|
||||
Keyword Args:
|
||||
autostart (bool, optional): If this is set, and the task has not been started yet,
|
||||
it will be started by this check. This is mainly used internally.
|
||||
**kwargs: Will be passed to the stage function, if one is called.
|
||||
|
||||
Returns:
|
||||
tuple: A tuple (dt, stage) where `dt` is the time-delta (in seconds) since the test
|
||||
|
|
@ -268,7 +271,7 @@ class OnDemandTask:
|
|||
|
||||
if stage_func:
|
||||
try:
|
||||
stage_func(self)
|
||||
stage_func(self, **kwargs)
|
||||
except Exception as err:
|
||||
logger.log_trace(
|
||||
f"Error getting stage of on-demand task {self} "
|
||||
|
|
@ -302,16 +305,17 @@ class OnDemandTask:
|
|||
|
||||
return dt, stage
|
||||
|
||||
def get_dt(self):
|
||||
def get_dt(self, **kwargs):
|
||||
"""
|
||||
Get the time-delta since last check.
|
||||
|
||||
Returns:
|
||||
int: The time since the last check, or 0 if this is the first time the task is checked.
|
||||
**kwargs: Will be passed to the stage function, if one is called.
|
||||
|
||||
"""
|
||||
|
||||
return self.check()[0]
|
||||
return self.check(autostart=True, **kwargs)[0]
|
||||
|
||||
def set_dt(self, dt):
|
||||
"""
|
||||
|
|
@ -330,16 +334,17 @@ class OnDemandTask:
|
|||
"""
|
||||
self.start_time = OnDemandTask.runtime() - dt
|
||||
|
||||
def get_stage(self):
|
||||
def get_stage(self, **kwargs):
|
||||
"""
|
||||
Get the current stage of the task. If no stage was given, this will return `None` but
|
||||
still update the last_checked time.
|
||||
|
||||
Returns:
|
||||
str or None: The current stage of the task, or `None` if no stages are set.
|
||||
**kwargs: Will be passed to the stage function, if one is called.
|
||||
|
||||
"""
|
||||
return self.check()[1]
|
||||
return self.check(autostart=True, **kwargs)[1]
|
||||
|
||||
def set_stage(self, stage=None):
|
||||
"""
|
||||
|
|
@ -386,14 +391,14 @@ class OnDemandHandler:
|
|||
This should be automatically called when Evennia starts.
|
||||
|
||||
"""
|
||||
self.tasks = dict(ServerConfig.objects.conf("on_demand_timers", default=dict))
|
||||
self.tasks = dict(ServerConfig.objects.conf(ONDEMAND_HANDLER_SAVE_NAME, default=dict))
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Save the on-demand timers to ServerConfig storage. Should be called when Evennia shuts down.
|
||||
|
||||
"""
|
||||
ServerConfig.objects.conf("on_demand_timers", self.tasks)
|
||||
ServerConfig.objects.conf(ONDEMAND_HANDLER_SAVE_NAME, self.tasks)
|
||||
|
||||
def _build_key(self, key, category):
|
||||
"""
|
||||
|
|
@ -404,7 +409,8 @@ class OnDemandHandler:
|
|||
called without arguments. If an Object, will be converted to a string. If
|
||||
an `OnDemandTask`, then all other arguments are ignored and the task will be used
|
||||
to build the internal storage key.
|
||||
category (str or callable): The task category. If callable, it will be called without arguments.
|
||||
category (str or callable): The task category. If callable, it will be called without
|
||||
arguments.
|
||||
|
||||
Returns:
|
||||
tuple (str, str or None): The unique key.
|
||||
|
|
@ -437,7 +443,8 @@ class OnDemandHandler:
|
|||
|
||||
Returns:
|
||||
OnDemandTask: The created task (or the same that was added, if given an `OnDemandTask`
|
||||
as a `key`). Use `task.get_dt()` and `task.get_stage()` to get data from it manually.
|
||||
as a `key`). Use `task.get_dt()` and `task.get_stage()` to get data from it
|
||||
manually.
|
||||
|
||||
"""
|
||||
if isinstance(key, OnDemandTask):
|
||||
|
|
@ -463,9 +470,10 @@ class OnDemandHandler:
|
|||
Remove an on-demand task.
|
||||
|
||||
Args:
|
||||
key (str, callable, OnDemandTask or Object): The unique identifier for the task. If a callable, will
|
||||
be called without arguments. If an Object, will be converted to a string. If an `OnDemandTask`,
|
||||
then all other arguments are ignored and the task will be used to identify the task to remove.
|
||||
key (str, callable, OnDemandTask or Object): The unique identifier for the task. If a
|
||||
callable, will be called without arguments. If an Object, will be converted to a
|
||||
string. If an `OnDemandTask`, then all other arguments are ignored and the task
|
||||
will be used to identify the task to remove.
|
||||
category (str or callable, optional): The category of the task.
|
||||
|
||||
Returns:
|
||||
|
|
@ -517,10 +525,10 @@ class OnDemandHandler:
|
|||
Clear all on-demand tasks.
|
||||
|
||||
Args:
|
||||
category (str, optional): The category of the tasks to clear. What `None` means is determined
|
||||
by the `all_on_none` kwarg.
|
||||
all_on_none (bool, optional): Determines what to clear if `category` is `None`. If `True`,
|
||||
clear all tasks, if `False`, only clear tasks with no category.
|
||||
category (str, optional): The category of the tasks to clear. What `None` means is
|
||||
determined by the `all_on_none` kwarg.
|
||||
all_on_none (bool, optional): Determines what to clear if `category` is `None`. If
|
||||
`True`, clear all tasks, if `False`, only clear tasks with no category.
|
||||
|
||||
"""
|
||||
if category is None and all_on_none:
|
||||
|
|
@ -538,9 +546,9 @@ class OnDemandHandler:
|
|||
|
||||
Args:
|
||||
key (str, callable, OnDemandTask or Object): The unique identifier for the task. If a
|
||||
callable, will be called without arguments. If an Object, will be converted to a string.
|
||||
If an `OnDemandTask`, then all other arguments are ignored and the task will be used
|
||||
(only useful to check the task is the same).
|
||||
callable, will be called without arguments. If an Object, will be converted to a
|
||||
string. If an `OnDemandTask`, then all other arguments are ignored and the task
|
||||
will be used (only useful to check the task is the same).
|
||||
|
||||
category (str, optional): The category of the task. If unset, this will only return
|
||||
tasks with no category.
|
||||
|
|
@ -551,22 +559,23 @@ class OnDemandHandler:
|
|||
"""
|
||||
return self.tasks.get(self._build_key(key, category))
|
||||
|
||||
def get_dt(self, key, category=None):
|
||||
def get_dt(self, key, category=None, **kwargs):
|
||||
"""
|
||||
Get the time-delta since the task started.
|
||||
|
||||
Args:
|
||||
key (str, callable, OnDemandTask or Object): The unique identifier for the task. If a
|
||||
callable, will be called without arguments. If an Object, will be converted to a string.
|
||||
If an `OnDemandTask`, then all other arguments are ignored and the task will be used
|
||||
to identify the task to get the time-delta from.
|
||||
callable, will be called without arguments. If an Object, will be converted to a
|
||||
string. If an `OnDemandTask`, then all other arguments are ignored and the task
|
||||
will be used to identify the task to get the time-delta from.
|
||||
**kwargs: Will be passed to the stage function, if one is called.
|
||||
|
||||
Returns:
|
||||
int or None: The time since the last check, or `None` if no task was found.
|
||||
|
||||
"""
|
||||
task = self.get(key, category)
|
||||
return task.get_dt() if task else None
|
||||
return task.get_dt(**kwargs) if task else None
|
||||
|
||||
def set_dt(self, key, category, dt):
|
||||
"""
|
||||
|
|
@ -576,9 +585,9 @@ class OnDemandHandler:
|
|||
|
||||
Args:
|
||||
key (str, callable, OnDemandTask or Object): The unique identifier for the task. If a
|
||||
callable, will be called without arguments. If an Object, will be converted to a string.
|
||||
If an `OnDemandTask`, then all other arguments are ignored and the task will be used
|
||||
to identify the task to set the time-delta for.
|
||||
callable, will be called without arguments. If an Object, will be converted to a
|
||||
string. If an `OnDemandTask`, then all other arguments are ignored and the task will
|
||||
be used to identify the task to set the time-delta for.
|
||||
category (str, optional): The category of the task.
|
||||
dt (int): The time-delta to set. This is an absolute value in seconds, same as returned
|
||||
by `get_dt`.
|
||||
|
|
@ -592,22 +601,24 @@ class OnDemandHandler:
|
|||
if task:
|
||||
task.set_dt(dt)
|
||||
|
||||
def get_stage(self, key, category=None):
|
||||
def get_stage(self, key, category=None, **kwargs):
|
||||
"""
|
||||
Get the current stage of an on-demand task.
|
||||
|
||||
Args:
|
||||
key (str, callable, OnDemandTask or Object): The unique identifier for the task. If a
|
||||
callable, will be called without arguments. If an Object, will be converted to a string.
|
||||
If an `OnDemandTask`, then all other arguments are ignored and the task will be used
|
||||
to identify the task to get the stage from.
|
||||
callable, will be called without arguments. If an Object, will be converted to a
|
||||
string. If an `OnDemandTask`, then all other arguments are ignored and the task
|
||||
will be used to identify the task to get the stage from.
|
||||
category (str, optional): The category of the task.
|
||||
**kwargs: Will be passed to the stage function, if one is called.
|
||||
|
||||
Returns:
|
||||
str or None: The current stage of the task, or `None` if no task was found.
|
||||
|
||||
"""
|
||||
task = self.get(key, category)
|
||||
return task.get_stage() if task else None
|
||||
return task.get_stage(**kwargs) if task else None
|
||||
|
||||
def set_stage(self, key, category=None, stage=None):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -675,3 +675,47 @@ class TestOnDemandHandler(EvenniaTest):
|
|||
self.assertEqual(self.handler.get_dt("daffodil", "flower"), 150)
|
||||
self.assertEqual(self.handler.get_stage("rose", "flower"), "bud")
|
||||
self.assertEqual(self.handler.get_stage("daffodil", "flower"), "wilted")
|
||||
|
||||
@staticmethod
|
||||
def _do_decay(task, **kwargs):
|
||||
task.stored_kwargs = kwargs
|
||||
|
||||
def test_handler_save(self):
|
||||
"""
|
||||
Testing the save method of the OnDemandHandler class for reported pickling issue
|
||||
|
||||
"""
|
||||
|
||||
self.handler.add(
|
||||
key="foo",
|
||||
category="decay",
|
||||
stages={
|
||||
0: "new",
|
||||
10: ("old", self._do_decay),
|
||||
},
|
||||
)
|
||||
self.handler.save()
|
||||
self.handler.clear()
|
||||
self.handler.save()
|
||||
|
||||
@mock.patch("evennia.scripts.ondemandhandler.OnDemandTask.runtime")
|
||||
def test_call_staging_function_with_kwargs(self, mock_runtime):
|
||||
""" """
|
||||
|
||||
mock_runtime.return_value = 0
|
||||
|
||||
self.handler.add(
|
||||
key="foo",
|
||||
category="decay",
|
||||
stages={
|
||||
0: "new",
|
||||
10: ("old", self._do_decay),
|
||||
},
|
||||
)
|
||||
self.handler.set_dt("foo", "decay", 10)
|
||||
|
||||
self.handler.get_stage("foo", "decay", foo="bar", bar="foo")
|
||||
|
||||
self.assertEqual(
|
||||
self.handler.get("foo", "decay").stored_kwargs, {"foo": "bar", "bar": "foo"}
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue