evennia.scripts.ondemandhandler

Helper to handle on-demand requests, allowing a system to change state only when a player or system 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.

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 when they are in the same room as it (so it can be incorporated in the room description). This is where on-demand handling comes in.

This is the basic principle, using the flowering system as an example.

  1. Someone plants a seed in a room (could also be automated). The seed is in a “seedling” state.

    We store the time it was planted (this is the important bit).

  2. A player enters the room or looks at the plant. We check the time it was planted, and calculate

how much time has passed since it was planted. If enough time has passed, we change the state to “sprouting” and probably change its description to reflect this.

  1. If a player looks at the plant and not enough time has passed, it keeps the last updated state.

  2. Eventually, it will be bloom time, and the plant will change to a “blooming” state when the player looks.

  3. If no player ever comes around to look at the plant, it will never change state, and if they show up after a long time, it may not show as a “wilted” state or be outright deleted when observed, 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.

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 in the room, they should get a message like ‘the plant suddenly blooms’ (or, more commonly, for messages like ‘you are feeling hungry’). For this you still probably need to use one of Evennia’s built-in timers or tickers instead. But most of the time you should really consider using on-demand handling instead.

Usage

from evennia import ON_DEMAND_HANDLER

# create a new on-demand task

flower = create_object(Flower, key="rose")

ON_DEMAND_HANDLER.add_task(
    flower, category="flowering",
    stages={0: "seedling", 120: "sprouting",
            300: "blooming", 600: "wilted", 700: "dead"})

# later, when we want to check the state of the plant (e.g. in a command),

state = ON_DEMAND_HANDLER.get_stage("flowering", last_checked=plant.planted_time)
class evennia.scripts.ondemandhandler.OnDemandTask(key, category, stages=None, autostart=True)[source]

Bases: object

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.

static runtime()[source]

Wraps the gametime.runtime() function.

Need to import here to avoid circular imports during server reboot. It’s a callable to allow easier unit testing.

static stagefunc_loop(task, **kwargs)[source]

Attach this to the last stage to have the task start over from the beginning

Example

stages = {0: “seedling”, 120: “flowering”, 300: “dead”, (“_loop”, OnDemandTask.stagefunc_loop)}

Note that the “respawn” state will never actually be visible as a state to the user, instead once it reaches this state, it will immediately loop and the new looped state will be shown and returned to the user. So it can an idea to mark that end state with a _ just to indicate this fact.

static stagefunc_bounce(task, **kwargs)[source]

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 for the bounce to continue indefinitely.

Example

stages = {0: (“cool”, OnDemandTask.stagefunc_bounce),

50: “lukewarm”, 150: “warm”, 300: “hot”, 300: (“HOT!”, OnDemandTask.stagefunc_bounce)}

default_stage_function = None
__init__(key, category, stages=None, autostart=True)[source]
Parameters
  • key (str) – A unique identifier for the task.

  • stages (dict, optional) – A dictionary {dt: str} or {int or float: (str, callable)} of time-deltas (in seconds) and the stage name they represent. If the value is a tuple, the first element is the name of the stage and the second is a callable that will be called when that stage is first reached. Warning: This callable is only triggered if the stage is actually checked/retrieved while the task is in that stage checks - it’s _not_ guaranteed to be called, even if the task time-wise goes through all its stages. Each callable must be picklable (so normally it should be a stand-alone function), and takes one argument - this OnDemandTask, which it can be modified in-place as needed. This can be used to loop a task or do other changes to the task.

  • autostart (bool, optional) – If last_checked is None, and this is False, then the time will not start counting until the first call of get_dt or get_stage. If True, creating the task will immediately make a hidden check and start the timer.

Examples

stages = {0: “seedling”,

120: “sprouting”, 300: “blooming”, 600: “wilted”, 700: “dead”

}

check(autostart=True, **kwargs)[source]

Check the current stage of the task and return the time-delta to the next stage.

Keyword Arguments
  • 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 started (or since it started its latest iteration). and stage is the name of the current stage. If no stages are defined, stage will always be None. Use get_dt and get_stage to get only one of these values.

get_dt(**kwargs)[source]

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.

set_dt(dt)[source]

Set the time-delta since the task started manually. This allows you to ‘cheat’ the system and set the time manually. This is useful for testing or when a system manipulates the state somehow (like using a potion that speeds up the growth of a plant).

Parameters

dt (int) – The time-delta to set. This is an absolute value in seconds, same as returned by get_dt.

Notes

Setting this will not on its own trigger any stage functions - this will only happen as normal, next time the state is checked and the stage is found to have changed.

get_stage(**kwargs)[source]

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.

set_stage(stage=None)[source]

Set the stage of the task manually. This allows you to ‘cheat’ the system and set the stage manually. This is useful for testing or when a system manipulates the state somehow (like using a potion that speeds up the growth of a plant). The given stage must be previously created for the given task. If task has no stages, this will do nothing.

Parameters

stage (str, optional) – The stage to set. If None, the task will be reset to its initial (first) state.

Notes

Setting this will not on its own trigger any stage functions - this will only happen as normal, next time the state is checked and the stage is found to have changed.

class evennia.scripts.ondemandhandler.OnDemandHandler[source]

Bases: object

A singleton handler for managing on-demand state changes. Its main function is to persistently track the time (in seconds) between a state change and the next. How you make use of this information is up to your particular system.

Contrary to just using the time module, this will also account for server restarts.

__init__()[source]

Initialize self. See help(type(self)) for accurate signature.

load()[source]

Load the on-demand timers from ServerConfig storage.

This should be automatically called when Evennia starts.

save()[source]

Save the on-demand timers to ServerConfig storage. Should be called when Evennia shuts down.

add(key, category=None, stages=None, autostart=True)[source]

Add a new on-demand task.

Parameters
  • key (str, callable, OnDemandTask or Object) – A unique identifier for the task. If this is a callable, it will be called without arguments. If a db-Object, it will be converted to a string representation (which will include its (#dbref). If an OnDemandTask, then all other arguments are ignored and the task is simply added as-is.

  • category (str or callable, optional) – A category to group the task under. If given, it must also be given when checking the task.

  • stages (dict, optional) – A dictionary {dt: str}, of time-deltas (in seconds) and the stage which should be entered after that much time has passed. autostart (bool,

  • optional) – If True, creating the task will immediately make a hidden check and start the timer.

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.

batch_add(*tasks)[source]

Add multiple on-demand tasks at once.

Parameters

*tasks (OnDemandTask) – A set of OnDemandTasks to add.

remove(key, category=None)[source]

Remove an on-demand task.

Parameters
  • 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

OnDemandTask or None – The removed task, or None if no task was found.

batch_remove(*keys, category=None)[source]

Remove multiple on-demand tasks at once, potentially within a given category.

Parameters
  • *keys (str, callable, OnDemandTask or Object) – The unique identifiers for the tasks. 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 tasks.

all(category=None, all_on_none=True)[source]

Get all on-demand tasks.

Parameters
  • category (str, optional) – The category of the tasks.

  • all_on_none (bool, optional) – Determines what to return if category is None. If True, all tasks will be returned. If False, only tasks without a category will be returned.

Returns

dict – A dictionary of all on-demand task, on the form {(key, category): task), …}. Use task.get_dt() or task.get_stage() to get the time-delta or stage of each task manually.

clear(category=None, all_on_none=True)[source]

Clear all on-demand tasks.

Parameters
  • 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.

get(key, category=None)[source]

Get an on-demand task. This will _not_ check it.

Parameters
  • 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).

  • category (str, optional) – The category of the task. If unset, this will only return tasks with no category.

Returns

OnDemandTask or None – The task, or None if no task was found.

get_dt(key, category=None, **kwargs)[source]

Get the time-delta since the task started.

Parameters
  • 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.

  • **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.

set_dt(key, category, dt)[source]

Set the time-delta since the task started manually. This allows you to ‘cheat’ the system and set the time manually. This is useful for testing or when a system manipulates the state somehow (like using a potion that speeds up the growth of a plant).

Parameters
  • 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.

  • 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.

Notes

Setting this will not on its own trigger any stage functions - this will only happen as normal, next time the state is checked and the stage is found to have changed.

get_stage(key, category=None, **kwargs)[source]

Get the current stage of an on-demand task.

Parameters
  • 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.

  • 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.

set_stage(key, category=None, stage=None)[source]

Set the stage of an on-demand task manually. This allows you to ‘cheat’ the system and set the stage manually. This is useful for testing or when a system manipulates the state somehow (like using a potion that speeds up the growth of a plant). The given stage must be previously created for the given task. If task has no stages, this will do nothing.

Parameters
  • 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 stage for.

  • category (str, optional) – The category of the task.

  • stage (str, optional) – The stage to set. If None, the task will be reset to its initial (first) state.

Notes

Setting this will not on its own trigger any stage functions - this will only happen as normal, next time the state is checked and the stage is found to have changed.