mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Fixing typos/expanding on the OnDemandHandler doc page
This commit is contained in:
parent
0593144e8d
commit
1ca384ac60
3 changed files with 38 additions and 22 deletions
|
|
@ -30,7 +30,7 @@ Feb 25, 2024
|
|||
setting (Griatch)
|
||||
- Doc fixes (InspectorCaracal, Griatch)
|
||||
|
||||
[new-ondemandhandler]: [https://www.evennia.com/docs/latest/Components/OnDemandHandler.html]
|
||||
[new-ondemandhandler]: https://www.evennia.com/docs/latest/Components/OnDemandHandler.html
|
||||
[pull3412]: https://github.com/evennia/evennia/pull/3412
|
||||
[pull3423]: https://github.com/evennia/evennia/pull/3423
|
||||
[pull3425]: https://github.com/evennia/evennia/pull/3425
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ Feb 25, 2024
|
|||
setting (Griatch)
|
||||
- Doc fixes (InspectorCaracal, Griatch)
|
||||
|
||||
[new-ondemandhandler][https://www.evennia.com/docs/latest/Components/OnDemandHandler.html]
|
||||
[new-ondemandhandler]: https://www.evennia.com/docs/latest/Components/OnDemandHandler.html
|
||||
[pull3412]: https://github.com/evennia/evennia/pull/3412
|
||||
[pull3423]: https://github.com/evennia/evennia/pull/3423
|
||||
[pull3425]: https://github.com/evennia/evennia/pull/3425
|
||||
|
|
|
|||
|
|
@ -4,19 +4,21 @@ This handler offers help for implementing on-demand state changes. On-demand mea
|
|||
|
||||
Take for example a gardening system. A player goes to a room and plants a seed. After a certain time, that plant will then move through a set of stages; it will move from "seedling" to 'sprout' to 'flowering' and then on to 'wilting' and eventually 'dead'.
|
||||
|
||||
Now, you _could_ use `utils.delay` to track each phase, or use the [TickerHandler](./TickerHandler.md) to tick the flower. You could even use a [Script](./Scripts.md) on the flower.
|
||||
Now, you _could_ use `utils.delay` to track each phase, or use the [TickerHandler](./TickerHandler.md) to tick the flower. You could even use a [Script](./Scripts.md) on the flower. This would work like this:
|
||||
|
||||
1. The ticker/task/Script would automatically fire at regular intervals to update the plant through its stages.
|
||||
2. Whenever a player comes to the room, the state is already updated on the flower, so they just read the state.
|
||||
|
||||
This will work fine, but if no one comes back to that room, that's a lot of updating that no one will see. While maybe not a big deal for a single player, what if you have flowers in thousands of rooms, all growing indepedently? Or some even more complex system requiring calculation on every state change. You should avoid spending computing on things that bring nothing extra to your player base.
|
||||
|
||||
Using the The on-demand style would instead work like this for the flower:
|
||||
1. When the player plants the seed, we register a new on-demand task with the `OnDemandHandler` (described below). This registers _the current timestamp_ when the plant starts to grow.
|
||||
2. When a player enters the room and/or looks at the plant, _then_ (and only then) we call the `OnDemandHandler` to see what state the flower it's in. It will then use the _current time_ to figure out how much time passed and which state the plant is thus in. Until someone looks, the plant is in its previous found state, because no-one needed to know until then. Same thing, if some other system needs to know this - they just figure out the state on the fly.
|
||||
Using the The on-demand style, the flower would instead work like this:
|
||||
|
||||
1. When the player plants the seed, we register _the current timestamp_ - the time the plant starts to grow. We store this with the `OnDemandHandler` (below).
|
||||
2. When a player enters the room and/or looks at the plant (or a code system needs to know the plant's state), _then_ (and only then) we check _the current time_ to figure out the state the flower must now be in (the `OnDemandHandler` does the book-keeping for us). The key is that _until we check_, the flower object is completely inactive and uses no computing resources.
|
||||
|
||||
## A blooming flower using the OnDemandHandler
|
||||
|
||||
This handler is found as `evennia.ON_DEMAND_HANDLER`. It is meant to be integrated into your other code. Here's an example of a flower that
|
||||
This handler is found as `evennia.ON_DEMAND_HANDLER`. It is meant to be integrated into your other code. Here's an example of a flower that goes through its stages of life in 12 hours.
|
||||
|
||||
```python
|
||||
# e.g. in mygame/typeclasses/objects.py
|
||||
|
|
@ -65,7 +67,7 @@ class Flower(Object):
|
|||
```
|
||||
|
||||
|
||||
You could now create the rose and it would figure out its state only when you are actually looking at it. It will stay a seedling for 10 minutes (of in-game real time) before it sprouts. Within 12 hours it will be dead again (a very quickly growing rose!).
|
||||
You could now create the rose and it would figure out its state only when you are actually looking at it. It will stay a seedling for 10 minutes (of in-game real time) before it sprouts. Within 12 hours it will be dead again.
|
||||
|
||||
If you had a `harvest` command in your game, you could equally have it check the stage of bloom and give you different results depending on if you pick the rose at the right time or not.
|
||||
|
||||
|
|
@ -87,9 +89,12 @@ ON_DEMAND_HANDLER.remove("key", category=None)
|
|||
ON_DEMAND_HANDLER.clear(cateogory="category") #clear all with category
|
||||
```
|
||||
|
||||
```{sidebar} Not all stages may fire!
|
||||
This is important. If no-one checks in on the flower until a time when it's already wilting, it will simply _skip_ all its previous stages, directly to the 'wilting' stage. So don't write code for a stage that assumes previous stages to have made particular changes to the object - those changes may not have happened because those stages could have been skipped entirely!
|
||||
```
|
||||
- The `key` can be a string, but also a typeclassed object (its string representation will be used, which normally includes its `#dbref`). You can also pass a `callable` - this will be called without arguments and is expected to return a string to use for the `key`. Finally, you can also pass [OnDemandTask](evennia.scripts.ondemandhandler.OnDemandTask) entities - these are the objects the handler uses under the hood to represent each task.
|
||||
- The `category` allows you to further categorize your demandhandler tasks to make sure they are unique. Since the handler is global, you need to make sure `key` + `category` is unique. While `category` is optional, if you use it you must also use it to retrieve your state later.
|
||||
- `stages` is a `dict` `{dt: statename}` or `{dt: (statename, callable}` that represents how much time (in seconds) before next stage begins. In the flower example above, it was 10 hours until the `wilting` state began. If a `callable` is also included, this will be called *the first time* that state is checked for. The callable takes a `evennia.OnDemandTask` as an argument and allows for tweaking the task on the fly. The `dt` can also be a `float` if you desire higher than per-second precision. Having `stages` is optional - sometimes you only want to know how much time has passed.
|
||||
- `stages` is a `dict` `{dt: statename}` or `{dt: (statename, callable}` that represents how much time (in seconds) from _the start of the task_ to that stage to begin. In the flower example above, it was 10 hours until the `wilting` state began. If a `callable` is also included, this will be called *the first time* that state is checked for (only!). The callable takes a `evennia.OnDemandTask` as an argument and allows for tweaking the task on the fly. The `dt` can also be a `float` if you desire higher than per-second precision. Having `stages` is optional - sometimes you only want to know how much time has passed.
|
||||
- `.get_dt()` - get the current time (in seconds) since the task started. This is a `float`.
|
||||
- `.get_stage()` - get the current state name, such as "flowering" or "seedling". If you didn't specify any `stages`, this will return `None`, and you need to interpret the `dt` yourself to determine which state you are in.
|
||||
|
||||
|
|
@ -99,22 +104,23 @@ Under the hood, the handler uses [OnDemandTask](evennia.scripts.ondemandhandler
|
|||
```python
|
||||
from evennia import ON_DEMAND_HANDLER, OnDemandTask
|
||||
|
||||
task1 = OnDemandTask("key1", {0: "state1", 100: "state2"})
|
||||
task2 = OnDemandTask("key2", category)
|
||||
task1 = OnDemandTask("key1", {0: "state1", 100: ("state2", my_callable)})
|
||||
task2 = OnDemandTask("key2", category="state-category")
|
||||
|
||||
# batch-start on-demand tasks
|
||||
ON_DEMAND_HANDLER.batch_add(task1, task2)
|
||||
|
||||
# get tasks back
|
||||
task = ON_DEMAND_HANDLER.get("key1")
|
||||
# get the tasks back later
|
||||
task1 = ON_DEMAND_HANDLER.get("key1")
|
||||
task2 = ON_DEMAND_HANDLER.get("key1", category="state-category")
|
||||
|
||||
# batch-delete (deactivate) from handler
|
||||
# batch-deactivate tasks you have available
|
||||
ON_DEMAND_HANDLER.batch_remove(task1, task2)
|
||||
```
|
||||
|
||||
### Looping repeatedly
|
||||
|
||||
Normally, when a sequence of `stages` have been cycled through, the task will just
|
||||
|
||||
Normally, when a sequence of `stages` have been cycled through, the task will just stop at the last stage indefinitely.
|
||||
|
||||
`evennia.OnDemandTask.stagefunc_loop` is an included static-method callable you can use to make the task loop. Here's an example of how to use it:
|
||||
|
||||
|
|
@ -133,17 +139,17 @@ ON_DEMAND_HANDLER.add(
|
|||
)
|
||||
```
|
||||
|
||||
This is a trap state that loops through its states depending on timing. Note that the looping helper callable will _immediately_ reset the cycle back to the first stage, so the last stage will never be visible to the player/game system. So it's a good (if optional) idea to name it with `_*` to remember this is a 'virtual' stage.
|
||||
This is a trap state that loops through its states depending on timing. Note that the looping helper callable will _immediately_ reset the cycle back to the first stage, so the last stage will never be visible to the player/game system. So it's a good (if optional) idea to name it with `_*` to remember this is a 'virtual' stage. In the example above, the "deadly" state will cycle directly to "harmless".
|
||||
|
||||
The `OnDemandTask` task instance has a `.iterations` variable that will go up by one for every loop.
|
||||
|
||||
If the state is not checked for a long time, the looping function will correctly update the `.iterations` of the task it would have used so far and figure out where in the cycle it is right now.
|
||||
If the state is not checked for a long time, the looping function will correctly update the `.iterations` property on the task it would have used so far and figure out where in the cycle it is right now.
|
||||
|
||||
### Bouncing back and forth
|
||||
|
||||
`evennia.OnDemandTask.stagefunc_bounce` is an included static-method callable you can use to 'bounce' the sequence of stages. That is, it will cycle to the end of the cycle and then reverse direction and cycle through the sequence in reverse.
|
||||
|
||||
To make this repreat indefinitely, you need to put the callables at both ends of the list:
|
||||
To make this repeat indefinitely, you need to put these callables at both ends of the list:
|
||||
|
||||
```python
|
||||
from evennia import ON_DEMAND_HANDLER, OnDemandTask
|
||||
|
|
@ -165,10 +171,20 @@ This will cycle
|
|||
|
||||
cold -> luke warm -> warm -> hot -> HOT!
|
||||
|
||||
before reversing and go back:
|
||||
before reversing and go back over and over:
|
||||
|
||||
HOT! -> hot -> warm -> luke warm -> cold
|
||||
|
||||
Over and over. The `OnDemandTask` instance has an `.iterations` property that will step up by one every time the sequence reverses.
|
||||
Unlike the `stagefunc_loop` callable, the bouncing one _will_ visibly stay at the first and last stage until it changes to the next one in the sequence. The `OnDemandTask` instance has an `.iterations` property that will step up by one every time the sequence reverses.
|
||||
|
||||
If the state is not checked for a long time, the bounce function will correctly update the `.iterations` property to the amount of iterations it would have done in that time, and figure out where in the cycle it must be right now.
|
||||
If the state is not checked for a long time, the bounce function will correctly update the `.iterations` property to the amount of iterations it would have done in that time, and figure out where in the cycle it is right now.
|
||||
|
||||
## When is it not suitable to do things on-demand?
|
||||
|
||||
If you put your mind to it, you can probably make of your game on-demand. The player will not be the wiser.
|
||||
|
||||
There is only really one case where on-demand doesn't work, and that is if the player should be informed of something _without first providing any input_.
|
||||
|
||||
If a player has to run `check health` command to see how much health they have, that could happen on demand. Similarly, a prompt could be set to update every time you move. But if you would an idling player to get a message popping up out of nowhere saying "You are feeling hungry" or to have some HP meter visually increasing also when standing still, then some sort of timer/ticker would be necessary to crank the wheels.
|
||||
|
||||
Remember however, that in a text-medium (especially with traditional line-by-line MUD clients), there is only so much spam you can push on the player before they get overwhelmed.
|
||||
Loading…
Add table
Add a link
Reference in a new issue