Add @interactive decorator to use yield to pause in a method

This commit is contained in:
Griatch 2019-04-21 14:23:36 +02:00
parent 866e8611b7
commit a2bc979503
2 changed files with 81 additions and 13 deletions

View file

@ -15,10 +15,10 @@ Update to Python 3
- Add new `@force` command to have another object perform a command. - Add new `@force` command to have another object perform a command.
- Add the Portal uptime to the `@time` command. - Add the Portal uptime to the `@time` command.
- Make the `@link` command first make a local search before a global search. - Make the `@link` command first make a local search before a global search.
- Have the default Unloggedin-look command look for optional `connection_screen()` callable in - Have the default Unloggedin-look command look for optional `connection_screen()` callable in
`mygame/server/conf/connection_screen.py`. This allows for more flexible welcome screens `mygame/server/conf/connection_screen.py`. This allows for more flexible welcome screens
that are calculated on the fly. that are calculated on the fly.
- `@py` command now defaults to escaping html tags in its output when viewing in the webclient. - `@py` command now defaults to escaping html tags in its output when viewing in the webclient.
Use new `/clientraw` switch to get old behavior (issue #1369). Use new `/clientraw` switch to get old behavior (issue #1369).
### Web ### Web
@ -73,7 +73,7 @@ Web/Django standard initiative (@strikaco)
### Prototypes ### Prototypes
- `evennia.prototypes.save_prototype` now takes the prototype as a normal - `evennia.prototypes.save_prototype` now takes the prototype as a normal
argument (`prototype`) instead of having to give it as `**prototype`. argument (`prototype`) instead of having to give it as `**prototype`.
- `evennia.prototypes.search_prototype` has a new kwarg `require_single=False` that - `evennia.prototypes.search_prototype` has a new kwarg `require_single=False` that
raises a KeyError exception if query gave 0 or >1 results. raises a KeyError exception if query gave 0 or >1 results.
@ -106,7 +106,7 @@ Web/Django standard initiative (@strikaco)
### Server ### Server
- Convert ServerConf model to store its values as a Picklefield (same as Attributes) instead of using a custom solution. - Convert ServerConf model to store its values as a Picklefield (same as Attributes) instead of using a custom solution.
- OOB: Add support for MSDP LIST, REPORT, UNREPORT commands (re-mapped to `msdp_list`, - OOB: Add support for MSDP LIST, REPORT, UNREPORT commands (re-mapped to `msdp_list`,
`msdp_report`, `msdp_unreport` inlinefuncs_) `msdp_report`, `msdp_unreport` inlinefuncs_)
- Added `evennia.ANSIString` to flat API. - Added `evennia.ANSIString` to flat API.
- Server/Portal log files now cycle to names on the form `server_.log_19_03_08_` instead of `server.log___19.3.8`, retaining - Server/Portal log files now cycle to names on the form `server_.log_19_03_08_` instead of `server.log___19.3.8`, retaining
@ -116,6 +116,9 @@ Web/Django standard initiative (@strikaco)
- `evennia` launcher now fully handles all django-admin commands, like running tests in parallel. - `evennia` launcher now fully handles all django-admin commands, like running tests in parallel.
- `evennia.utils.create.account` now also takes `tags` and `attrs` keywords. - `evennia.utils.create.account` now also takes `tags` and `attrs` keywords.
- `evennia.utils.interactive` decorator can now allow you to use yield(secs) to pause operation
in any function, not just in Command.func. Likewise, response = yield(question) will work
if the decorated function has an argument or kwarg `caller`.
- Added many more unit tests. - Added many more unit tests.
- Swap argument order of `evennia.set_trace` to `set_trace(term_size=(140, 40), debugger='auto')` - Swap argument order of `evennia.set_trace` to `set_trace(term_size=(140, 40), debugger='auto')`
since the size is more likely to be changed on the command line. since the size is more likely to be changed on the command line.
@ -134,7 +137,7 @@ Web/Django standard initiative (@strikaco)
### Contribs ### Contribs
- The `extended_room` contrib saw some backwards-incompatible refactoring: - The `extended_room` contrib saw some backwards-incompatible refactoring:
+ All commands now begin with `CmdExtendedRoom`. So before it was `CmdExtendedLook`, now + All commands now begin with `CmdExtendedRoom`. So before it was `CmdExtendedLook`, now
it's `CmdExtendedRoomLook` etc. it's `CmdExtendedRoomLook` etc.
+ The `detail` command was broken out of the `desc` command and is now a new, stand-alone command + The `detail` command was broken out of the `desc` command and is now a new, stand-alone command
@ -142,7 +145,7 @@ Web/Django standard initiative (@strikaco)
command works in the tutorial-world. command works in the tutorial-world.
+ The `detail` command now also supports deleting details (like the tutorial-world version). + The `detail` command now also supports deleting details (like the tutorial-world version).
+ The new `ExtendedRoomCmdSet` includes all the extended-room commands and is now the recommended way + The new `ExtendedRoomCmdSet` includes all the extended-room commands and is now the recommended way
to install the extended-room contrib. to install the extended-room contrib.
- Reworked `menu_login` contrib to use latest EvMenu standards. Now also supports guest logins. - Reworked `menu_login` contrib to use latest EvMenu standards. Now also supports guest logins.
- Mail contrib was refactored to have optional Command classes `CmdMail` for OOC+IC mail (added - Mail contrib was refactored to have optional Command classes `CmdMail` for OOC+IC mail (added
to the CharacterCmdSet and `CmdMailCharacter` for IC-only mailing between chars (added to CharacterCmdSet) to the CharacterCmdSet and `CmdMailCharacter` for IC-only mailing between chars (added to CharacterCmdSet)
@ -260,7 +263,7 @@ Web/Django standard initiative (@strikaco)
- `tb_items` - Extends `tb_equip` with item use with conditions/status effects. - `tb_items` - Extends `tb_equip` with item use with conditions/status effects.
- `tb_magic` - Extends `tb_equip` with spellcasting. - `tb_magic` - Extends `tb_equip` with spellcasting.
- `tb_range` - Adds system for abstract positioning and movement. - `tb_range` - Adds system for abstract positioning and movement.
- The `extended_room` contrib saw some backwards-incompatible refactoring: - The `extended_room` contrib saw some backwards-incompatible refactoring:
- All commands now begin with `CmdExtendedRoom`. So before it was `CmdExtendedLook`, now - All commands now begin with `CmdExtendedRoom`. So before it was `CmdExtendedLook`, now
it's `CmdExtendedRoomLook` etc. it's `CmdExtendedRoomLook` etc.
- The `detail` command was broken out of the `desc` command and is now a new, stand-alone command - The `detail` command was broken out of the `desc` command and is now a new, stand-alone command
@ -268,11 +271,11 @@ Web/Django standard initiative (@strikaco)
command works in the tutorial-world. command works in the tutorial-world.
- The `detail` command now also supports deleting details (like the tutorial-world version). - The `detail` command now also supports deleting details (like the tutorial-world version).
- The new `ExtendedRoomCmdSet` includes all the extended-room commands and is now the recommended way - The new `ExtendedRoomCmdSet` includes all the extended-room commands and is now the recommended way
to install the extended-room contrib. to install the extended-room contrib.
- Updates and some cleanup of existing contribs. - Updates and some cleanup of existing contribs.
### Internationalization ### Internationalization
- Polish translation by user ogotai - Polish translation by user ogotai

View file

@ -16,10 +16,11 @@ import math
import re import re
import textwrap import textwrap
import random import random
import pickle import inspect
from twisted.internet.task import deferLater
from os.path import join as osjoin from os.path import join as osjoin
from importlib import import_module from importlib import import_module
from importlib.util import find_spec, module_from_spec from importlib.util import find_spec
from inspect import ismodule, trace, getmembers, getmodule, getmro from inspect import ismodule, trace, getmembers, getmodule, getmro
from collections import defaultdict, OrderedDict from collections import defaultdict, OrderedDict
from twisted.internet import threads, reactor, task from twisted.internet import threads, reactor, task
@ -1981,3 +1982,67 @@ def get_all_typeclasses(parent=None):
typeclasses = {name: typeclass for name, typeclass in typeclasses.items() typeclasses = {name: typeclass for name, typeclass in typeclasses.items()
if inherits_from(typeclass, parent)} if inherits_from(typeclass, parent)}
return typeclasses return typeclasses
def interactive(func):
"""
Decorator to make a method pausable with yield(seconds)
and able to ask for user-input with response=yield(question).
For the question-asking to work, 'caller' must the name
of an argument or kwarg to the decorated function.
Note that this turns the method into a generator.
Example usage:
@interactive
def myfunc(caller):
caller.msg("This is a test")
# wait five seconds
yield(5)
# ask user (caller) a question
response = yield("Do you want to continue waiting?")
if response == "yes":
yield(5)
else:
# ...
"""
from evennia.utils.evmenu import get_input
def _process_input(caller, prompt, result, generator):
deferLater(reactor, 0, _iterate, generator, caller, response=result)
return False
def _iterate(generator, caller=None, response=None):
try:
if response is None:
value = next(generator)
else:
value = generator.send(response)
except StopIteration:
pass
else:
if isinstance(value, (int, float)):
delay(value, _iterate, generator, caller=caller)
elif isinstance(value, str):
if not caller:
raise ValueError("To retrieve input from a @pausable method, that method "
"must be called with a 'caller' argument)")
get_input(caller, value, _process_input, generator=generator)
else:
raise ValueError("yield(val) in a @pausable method must have an int/float as arg.")
def decorator(*args, **kwargs):
argnames = inspect.getfullargspec(func).args
caller = None
if 'caller' in argnames:
# we assume this is an object
caller = args[argnames.index('caller')]
ret = func(*args, **kwargs)
if isinstance(ret, types.GeneratorType):
_iterate(ret, caller)
else:
return ret
return decorator