-
Notifications
You must be signed in to change notification settings - Fork 71
Python
Since LDMud 3.5 the driver can be compiled with Python support. The primary purpose of the Python support is the implementation of additional efuns in Python. Python offers a wide range of additional libraries that can be made available to LPC code this way.
LDMud 3.5 and Python 3 (3.7 or later recommended) is needed. Python support must be explicitly activated when compiling LDMud:
./configure --enable-use-python
makeWhen LDMud is started a Python script is executed. The default script is startup.py in the mudlib directory. The default can be changed with the --with-python-script=... option for configure. A different script can also be given on the command line of ldmud:
./ldmud --python-script ../python/startup.pyThe location of the script is interpreted relative to the mudlib directory.
A simple startup script would be:
import ldmud
def hello_world():
print("Hello, world!")
return 1
ldmud.register_efun("hello", hello_world)This script adds an efun named hello() that will print a string on standard output of the driver (not to the player!) and return 1.
The script is executed once and only once at startup. There is no way to execute it again or start another .py file.
For any long running MUDs it is desirable to load additional efuns later or reload efuns to fix bugs. For this there is a Python package ldmud-efuns that searches Python packages for LDMud entry points. This package can be installed with pip3:
pip3 install --user ldmud-efunsThis package offers a startup function that can be used in the startup.py:
from ldmudefuns.startup import startup
startup()The startup function will search for any installed Python package that has LDMud efuns and load and register them. The package already has its own efun python_reload() that will be found and registered this way. This efun will do this process again and search for new efuns and reload any existing efuns.
If you want your own efun to be found by the startup function from the ldmud-efuns package, you'll need to create a Python package, too:
- Create an empty directory
- Create a subdirectory with the name of your package (eg.
ldmud_hello) - In the subdirectory put your implementation. You'll need at least a (possibly empty)
__init__.pythere. For example put ahello.pythere:def hello_world(): print("Hello, world!") return 1
- In the main directory (beside the
ldmud_helloin our example) put aREADME.mdwith a description and asetup.py:Theimport setuptools with open("README.md", "r") as fh: long_description = fh.read() setuptools.setup( name="ldmud-hello", version="0.0.1", author="Me", author_email="my@myself.org" description="Hello World efun for LDMud", long_description=long_description, long_description_content_type="text/markdown", packages=setuptools.find_packages(), entry_points={ 'ldmud_efun': [ 'hello = ldmud_hello.hello:hello_world', ] }, zip_safe=False, )
entry_pointsentryldmud_efuncontains a list of all efuns that the package offers. In this case the efunhellowill be implemented by the functionhello_worldin the moduleldmud_hello.hello. - Install the package
python3 setup.py install --user
- In a running LDMud instance call the efun
python_reload()that will then find the new efun. (Or reboot the mud, so the startup script will find it.)
Python code that is executed by LDMud can use the ldmud module.
import ldmudThis module is not available to standalone Python programs.
All LPC types are mapped to Python objects. The types int, float, string and bytes are converted to Python int, float, str and bytes. The other LPC types have their own type in the ldmud module: Object, LWObject, Array, Mapping, Struct, Closure, Symbol, QuotedArray, Lvalue.
Python data types like dict or list can be converted to the corresponding LPC Mapping or Array, but this must be done explicitly. There is no automatism that does this.
import ldmud
python_list = [1, 2, 3]
lpc_array = ldmud.Array(python_list)
python_list = list(lpc_array)The usual operations (array/mapping addition, struct member access, closure call) are available with these objects.
Python efuns can be annotated with type information to offer compile and runtime type checks:
def hello(title: str) -> int:
print("Hello, %s!" % (title,))
return 1Python code can call efuns and lfuns. These calls are done in the context of the calling LPC code (the LPC program that has called the Python efun will be regarded as this_object() for the efun resp. previous_object() for the lfun call).
Efun calls can be called using the ldmud.efuns namespace:
import ldmud
def hello_world() -> None:
ldmud.efuns.write("Hello, World!\n")Lfuns can be called using an ldmud.Object. It has a .functions member that contains all functions of the object.
import ldmud
def hello_world(ob: ldmud.Object) -> None:
ob.functions.send_message("Hello, World!")Beside registered efuns Python code can also be called by certain events. The following events can be handled with hooks:
- Heartbeats
- Object creation
- Object destruction
- Child process termination
To react on such an event a Python function has to be registered:
import ldmud
def ping():
print("Ping")
ldmud.register_hook(ldmud.ON_HEARTBEAT, ping)Further hook names are ON_OBJECT_CREATED, ON_OBJECT_DESTRUCTED and ON_CHILD_PROCESS_TERMINATED. For the object hooks the corresponding Python function will then get the object as an argument.
Additionally Python code can watch for non-blocking sockets:
import ldmud, os, select
pipe = os.pipe2(os.O_NONBLOCK | os.O_CLOEXEC)
def received(event):
print("Received: %s" % (os.read(pipe[0], 1024),))
ldmud.register_socket(pipe[0], received, select.POLLIN)Both types of event handlers will not automatically unregistered. If you're using a reload mechanism remember to unregister them when reloading. The ldmud-efuns package will call on_reload() when reloading:
def on_reload():
ldmud.unregister_socket(pipe[0])
os.close(pipe[0])Debugging is complicated because the Python scripts can not be executed outside the LDMud process. Therefore you might want to have a Python console in the MUD environment. The LDMud sources contain an example for it:
You'll need to load the Python script from your startup and use python_console efun like shown in the LPC snippet.
Be warned that this efun is a security risk. It lets you execute arbitrary Python statements and therefore should only be available in a private development environment.
LDMud does not support multithreading, Python however does. So if you're creating additional threads in Python, you are not allowed to use any LPC routines or datastructures. So copy any needed data into pure Python data structures before using it in another thread.
There is support for the Python asyncio module. For this you'll need to install the ldmud-asyncio package. It allows to execute Python coroutines within LDMud:
import ldmud, ldmud_asyncio, asyncio
async def do_exec(prog, cb):
proc = await asyncio.create_subprocess_exec(prog, stdout=asyncio.subprocess.PIPE)
async for line in proc.stdout:
cb(line.decode())
await proc.wait()
cb(0)
def efun_exec(prog: str, cb: ldmud.Closure) -> None:
# Execute <prog> and call <cb> with each line of its output.
asyncio.run(do_exec(prog, cb))
ldmud.register_efun("py_exec", efun_exec)