Batch Code Processor¶
The batch-command processor is a superuser-only function, invoked by
> @batchcode path.to.batchcodefile
path.to.batchcodefile is the path to a batch-code file. Such
a file should have a name ending in “
.py” (but you shouldn’t include
that in the path). The path is given like a python path relative to a
folder you define to hold your batch files, set by
in your settings. Default folder is (assuming your game is called
mygame/world/. So if you want to run the example batch
mygame/world/batch_code.py, you could simply use
> @batchcode batch_code
This will try to run through the entire batch file in one go. For more
gradual, interactive control you can use the
/debug will put the processor in debug mode. Read below
for more info.
The batch file¶
A batch-code file is a normal Python file. The difference is that since
the batch processor loads and executes the file rather than importing
it, you can reliably update the file, then call it again, over and over
and see your changes without needing to
@reload the server. This
makes for easy testing. In the batch-code file you have also access to
the following global variables:
caller- This is a reference to the object running the batchprocessor.
DEBUG- This is a boolean that lets you determine if this file is currently being run in debug-mode or not. See below how this can be useful.
Running a plain Python file through the processor will just execute the file from beginning to end. If you want to get more control over the execution you can use the processor’s interactive mode. This runs certain code blocks on their own, rerunning only that part until you are happy with it. In order to do this you need to add special markers to your file to divide it up into smaller chunks. These take the form of comments, so the file remains valid Python.
Here are the rules of syntax of the batch-command
#CODEas the first on a line marks the start of a code block. It will last until the beginning of another marker or the end of the file. Code blocks contain functional python code. Each
#CODEblock will be run in complete isolation from other parts of the file, so make sure it’s self-contained.
#HEADERas the first on a line marks the start of a header block. It lasts until the next marker or the end of the file. This is intended to hold imports and variables you will need for all other blocks .All python code defined in a header block will always be inserted at the top of every
#CODEblocks in the file. You may have more than one
#HEADERblock, but that is equivalent to having one big one. Note that you can’t exchange data between code blocks, so editing a header-variable in one code block won’t affect that variable in any other code block!
#INSERT path.to.filewill insert another batchcode (Python) file at that position.
#that is not starting a
#INSERTinstruction is considered a comment.
- Inside a block, normal Python syntax rules apply. For the sake of indentation, each block acts as a separate python module.
Below is a version of the example file found in
# # This is an example batch-code build file for Evennia. # #HEADER # This will be included in all other #CODE blocks from evennia.utils import create, search from evennia.contrib.tutorial_examples import red_button from typeclasses.objects import Object limbo = search.objects(caller, 'Limbo', global_search=True) #CODE red_button = create.create_object(red_button.RedButton, key="Red button", location=limbo, aliases=["button"]) # caller points to the one running the script caller.msg("A red button was created.") # importing more code from another batch-code file #INSERT batch_code_insert #CODE table = create.create_object(Object, key="Blue Table", location=limbo) chair = create.create_object(Object, key="Blue Chair", location=limbo) string = "A %s and %s were created." if DEBUG: table.delete() chair.delete() string += " Since debug was active, " \ "they were deleted again." caller.msg(string % (table, chair))
This uses Evennia’s Python API to create three objects in sequence.
Try to run the example script with
> @batchcode/debug tutorial_examples.example_batch_code
The batch script will run to the end and tell you it completed. You will
also get messages that the button and the two pieces of furniture were
created. Look around and you should see the button there. But you won’t
see any chair nor a table! This is because we ran this with the
/debug switch, which is directly visible as
the script. In the above example we handled this state by deleting the
chair and table again.
The debug mode is intended to be used when you test out a batchscript. Maybe you are looking for bugs in your code or try to see if things behave as they should. Running the script over and over would then create an ever-growing stack of chairs and tables, all with the same name. You would have to go back and painstakingly delete them later.
Interactive mode works very similar to the batch-command processor
counterpart. It allows you more step-wise control over how the batch
file is executed. This is useful for debugging or for picking and
choosing only particular blocks to run. Use
@batchcommand with the
/interactive flag to enter interactive mode.
> @batchcode/interactive tutorial_examples.batch_code
You should see the following:
01/02: red_button = create_object(red_button.RedButton, [...] (hh for help)
This shows that you are on the first
#CODE block, the first of only
two commands in this batch file. Observe that the block has not
actually been executed at this point!
To take a look at the full code snippet you are about to run, use
(a batch-processor version of
from evennia.utils import create, search from evennia.contrib.tutorial_examples import red_button from typeclasses.objects Object limbo = search.objects(caller, 'Limbo', global_search=True) red_button = create.create_object(red_button.RedButton, key="Red button", location=limbo, aliases=["button"]) # caller points to the one running the script caller.msg("A red button was created.")
Compare with the example code given earlier. Notice how the content of
#HEADER has been pasted at the top of the
#CODE block. Use
pp to actually execute this block (this will create the button and
give you a message). Use
nn (next) to go to the next command. Use
hh for a list of commands.
If there are tracebacks, fix them in the batch file, then use
reload the file. You will still be at the same code block and can rerun
it easily with
pp as needed. This makes for a simple debug cycle. It
also allows you to rerun individual troublesome blocks - as mentioned,
in a large batch file this can be very useful (don’t forget the
/debug mode either).
bb (next and back) to step through the file; e.g.
nn 12 will jump 12 steps forward (without processing any blocks in
between). All normal commands of Evennia should work too while working
in interactive mode.
Limitations and Caveats¶
The batch-code processor is by far the most flexible way to build a world in Evennia. There are however some caveats you need to keep in mind.
- Safety. Or rather the lack of it. There is a reason only superusers are allowed to run the batch-code processor by default. The code-processor runs without any Evennia security checks and allows full access to Python. If an untrusted party could run the code-processor they could execute arbitrary python code on your machine, which is potentially a very dangerous thing. If you want to allow other users to access the batch-code processor you should make sure to run Evennia as a separate and very limited-access user on your machine (i.e. in a ‘jail’). By comparison, the batch-command processor is much safer since the user running it is still ‘inside’ the game and can’t really do anything outside what the game commands allow them to.
- You cannot communicate between code blocks. Global variables
won’t work in code batch files, each block is executed as stand-alone
environments. Similarly you cannot in one
#CODEblock assign to variables from the
#HEADERblock and expect to be able to read the changes from another
#CODEblock (whereas a python execution limitation, allowing this would also lead to very hard-to-debug code when using the interactive mode). The main issue with this is when building e.g. a room in one code block and later want to connect that room with a room you built in another block. To do this, you must perform a database search for the name of the room you created (since you cannot know in advance which dbref it got assigned). This sounds iffy, but there is an easy way to handler this - use object aliases. You can assign any number of aliases to any object. Make sure that one of those aliases is unique (like “room56”) and you will henceforth be able to always find it later by searching for it from other code blocks regardless of if the main name is shared with hundreds of other rooms in your world (coincidentally, this is also one way of implementing “zones”, should you want to group rooms together).
- Defining TypeClasses or treating a batchcode file like a normal Python file will lead to ruin. Python batchcode files are syntactically valid Python modules. However, doing anything that would import them is the equivalent of running them in their entirety. Therefore you don’t want to define typeclasses in them, because any time Evennia would import the module to find the class, a whole host of new objects would be created by your batch code.
- Code that relies on the batch file’s real file path will fail. Batch code files are chopped up into code snippets and are executed based on the resulting strings and a custom dictionary context. This means that the code will lose association with any file it was once a part of.