2020-04-07 23:13:24 +02:00
|
|
|
# TickerHandler
|
|
|
|
|
|
|
|
|
|
|
2020-06-16 16:53:35 +02:00
|
|
|
One way to implement a dynamic MUD is by using "tickers", also known as "heartbeats". A ticker is a
|
|
|
|
|
timer that fires ("ticks") at a given interval. The tick triggers updates in various game systems.
|
2020-04-07 23:13:24 +02:00
|
|
|
|
|
|
|
|
## About Tickers
|
|
|
|
|
|
2020-06-16 16:53:35 +02:00
|
|
|
Tickers are very common or even unavoidable in other mud code bases. Certain code bases are even
|
|
|
|
|
hard-coded to rely on the concept of the global 'tick'. Evennia has no such notion - the decision to
|
|
|
|
|
use tickers is very much up to the need of your game and which requirements you have. The "ticker
|
|
|
|
|
recipe" is just one way of cranking the wheels.
|
2020-04-07 23:13:24 +02:00
|
|
|
|
2021-10-21 21:04:14 +02:00
|
|
|
The most fine-grained way to manage the flow of time is of course to use [Scripts](./Scripts.md). Many
|
2020-06-16 16:53:35 +02:00
|
|
|
types of operations (weather being the classic example) are however done on multiple objects in the
|
|
|
|
|
same way at regular intervals, and for this, storing separate Scripts on each object is inefficient.
|
|
|
|
|
The way to do this is to use a ticker with a "subscription model" - let objects sign up to be
|
|
|
|
|
triggered at the same interval, unsubscribing when the updating is no longer desired.
|
2020-04-07 23:13:24 +02:00
|
|
|
|
2020-06-16 16:53:35 +02:00
|
|
|
Evennia offers an optimized implementation of the subscription model - the *TickerHandler*. This is
|
|
|
|
|
a singleton global handler reachable from `evennia.TICKER_HANDLER`. You can assign any *callable* (a
|
|
|
|
|
function or, more commonly, a method on a database object) to this handler. The TickerHandler will
|
|
|
|
|
then call this callable at an interval you specify, and with the arguments you supply when adding
|
|
|
|
|
it. This continues until the callable un-subscribes from the ticker. The handler survives a reboot
|
|
|
|
|
and is highly optimized in resource usage.
|
2020-04-07 23:13:24 +02:00
|
|
|
|
|
|
|
|
Here is an example of importing `TICKER_HANDLER` and using it:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
# we assume that obj has a hook "at_tick" defined on itself
|
|
|
|
|
from evennia import TICKER_HANDLER as tickerhandler
|
|
|
|
|
|
|
|
|
|
tickerhandler.add(20, obj.at_tick)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
That's it - from now on, `obj.at_tick()` will be called every 20 seconds.
|
|
|
|
|
|
|
|
|
|
You can also import function and tick that:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
from evennia import TICKER_HANDLER as tickerhandler
|
|
|
|
|
from mymodule import myfunc
|
|
|
|
|
|
|
|
|
|
tickerhandler.add(30, myfunc)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Removing (stopping) the ticker works as expected:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
tickerhandler.remove(20, obj.at_tick)
|
|
|
|
|
tickerhandler.remove(30, myfunc)
|
|
|
|
|
```
|
|
|
|
|
|
2020-06-16 16:53:35 +02:00
|
|
|
Note that you have to also supply `interval` to identify which subscription to remove. This is
|
|
|
|
|
because the TickerHandler maintains a pool of tickers and a given callable can subscribe to be
|
|
|
|
|
ticked at any number of different intervals.
|
2020-04-07 23:13:24 +02:00
|
|
|
|
|
|
|
|
The full definition of the `tickerhandler.add` method is
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
tickerhandler.add(interval, callback,
|
|
|
|
|
idstring="", persistent=True, *args, **kwargs)
|
|
|
|
|
```
|
|
|
|
|
|
2020-06-16 16:53:35 +02:00
|
|
|
Here `*args` and `**kwargs` will be passed to `callback` every `interval` seconds. If `persistent`
|
|
|
|
|
is `False`, this subscription will not survive a server reload.
|
2020-04-07 23:13:24 +02:00
|
|
|
|
2020-06-16 16:53:35 +02:00
|
|
|
Tickers are identified and stored by making a key of the callable itself, the ticker-interval, the
|
|
|
|
|
`persistent` flag and the `idstring` (the latter being an empty string when not given explicitly).
|
2020-04-07 23:13:24 +02:00
|
|
|
|
2020-06-16 16:53:35 +02:00
|
|
|
Since the arguments are not included in the ticker's identification, the `idstring` must be used to
|
|
|
|
|
have a specific callback triggered multiple times on the same interval but with different arguments:
|
2020-04-07 23:13:24 +02:00
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
tickerhandler.add(10, obj.update, "ticker1", True, 1, 2, 3)
|
|
|
|
|
tickerhandler.add(10, obj.update, "ticker2", True, 4, 5)
|
|
|
|
|
```
|
|
|
|
|
|
2020-06-16 16:53:35 +02:00
|
|
|
> Note that, when we want to send arguments to our callback within a ticker handler, we need to
|
|
|
|
|
specify `idstring` and `persistent` before, unless we call our arguments as keywords, which would
|
|
|
|
|
often be more readable:
|
2020-04-07 23:13:24 +02:00
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
tickerhandler.add(10, obj.update, caller=self, value=118)
|
|
|
|
|
```
|
|
|
|
|
|
2020-06-16 16:53:35 +02:00
|
|
|
If you add a ticker with exactly the same combination of callback, interval and idstring, it will
|
|
|
|
|
overload the existing ticker. This identification is also crucial for later removing (stopping) the
|
|
|
|
|
subscription:
|
2020-04-07 23:13:24 +02:00
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
tickerhandler.remove(10, obj.update, idstring="ticker1")
|
|
|
|
|
tickerhandler.remove(10, obj.update, idstring="ticker2")
|
|
|
|
|
```
|
|
|
|
|
|
2020-06-16 16:53:35 +02:00
|
|
|
The `callable` can be on any form as long as it accepts the arguments you give to send to it in
|
|
|
|
|
`TickerHandler.add`.
|
2020-04-07 23:13:24 +02:00
|
|
|
|
2020-06-16 16:53:35 +02:00
|
|
|
> Note that everything you supply to the TickerHandler will need to be pickled at some point to be
|
|
|
|
|
saved into the database. Most of the time the handler will correctly store things like database
|
2021-10-21 21:04:14 +02:00
|
|
|
objects, but the same restrictions as for [Attributes](./Attributes.md) apply to what the TickerHandler
|
2020-06-16 16:53:35 +02:00
|
|
|
may store.
|
2020-04-07 23:13:24 +02:00
|
|
|
|
2020-06-16 16:53:35 +02:00
|
|
|
When testing, you can stop all tickers in the entire game with `tickerhandler.clear()`. You can also
|
|
|
|
|
view the currently subscribed objects with `tickerhandler.all()`.
|
2020-04-07 23:13:24 +02:00
|
|
|
|
2022-02-06 19:27:15 +01:00
|
|
|
See the [Weather Tutorial](../Howtos/Weather-Tutorial.md) for an example of using the TickerHandler.
|
2020-04-07 23:13:24 +02:00
|
|
|
|
|
|
|
|
### When *not* to use TickerHandler
|
|
|
|
|
|
2020-06-16 16:53:35 +02:00
|
|
|
Using the TickerHandler may sound very useful but it is important to consider when not to use it.
|
|
|
|
|
Even if you are used to habitually relying on tickers for everything in other code bases, stop and
|
|
|
|
|
think about what you really need it for. This is the main point:
|
2020-04-07 23:13:24 +02:00
|
|
|
|
|
|
|
|
> You should *never* use a ticker to catch *changes*.
|
|
|
|
|
|
2020-06-16 16:53:35 +02:00
|
|
|
Think about it - you might have to run the ticker every second to react to the change fast enough.
|
|
|
|
|
Most likely nothing will have changed at a given moment. So you are doing pointless calls (since
|
|
|
|
|
skipping the call gives the same result as doing it). Making sure nothing's changed might even be
|
|
|
|
|
computationally expensive depending on the complexity of your system. Not to mention that you might
|
|
|
|
|
need to run the check *on every object in the database*. Every second. Just to maintain status quo
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
Rather than checking over and over on the off-chance that something changed, consider a more
|
|
|
|
|
proactive approach. Could you implement your rarely changing system to *itself* report when its
|
|
|
|
|
status changes? It's almost always much cheaper/efficient if you can do things "on demand". Evennia
|
|
|
|
|
itself uses hook methods for this very reason.
|
|
|
|
|
|
|
|
|
|
So, if you consider a ticker that will fire very often but which you expect to have no effect 99% of
|
|
|
|
|
the time, consider handling things things some other way. A self-reporting on-demand solution is
|
|
|
|
|
usually cheaper also for fast-updating properties. Also remember that some things may not need to be
|
|
|
|
|
updated until someone actually is examining or using them - any interim changes happening up to that
|
|
|
|
|
moment are pointless waste of computing time.
|
|
|
|
|
|
|
|
|
|
The main reason for needing a ticker is when you want things to happen to multiple objects at the
|
|
|
|
|
same time without input from something else.
|