Skip to content

Build, run and hot reload

  1. Start the server once, with a plugins folder, and leave it running:

    Terminal window
    cd run
    ../build/bin/ramjet --data-dir ../data --plugins plugins
  2. In your plugin project, with RAMJET_PLUGINS set (see create a plugin), build:

    Terminal window
    dotnet build -c Release
  3. The build copies Homes.dll, Homes.pdb and plugin.json into run/plugins/Homes/. The server notices, waits 250 ms for the write to finish, and reloads the plugin:

    [INFO] hot reloading Homes.dll
    [INFO] [Homes] enabled v1.0.1 (id 1)
    [INFO] [Homes] disabled

    The new version is enabled before the old one is released, so there is no window in which your commands or event handlers are missing. Edit, build, test again: a few seconds per cycle.

If you do not set RAMJET_PLUGINS, copy bin/Release/net10.0/Homes.dll, Homes.pdb and plugin.json into plugins/Homes/ yourself.

  • ECS components with an unchanged layout keep their values on every entity. Change a field type or order and the values are dropped (and the component recreated), but only if the reloading plugin is the only one bound to the same component key; otherwise the registration fails and the log says why.
  • Entities are not owned by plugins, they survive.
  • Files in the plugin’s data folder (plugins/homes/data/) survive.
  • Plugin-owned objects (your fields, caches, timers) are gone: the plugin is a new instance. Keep state that must outlive a reload in components or in the data folder. See hot reload for the full rules.

The new assembly is loaded first and the old one is unloaded only if that worked. A compile error never produces a dll; an exception in the new OnEnable is logged and the previous version keeps running:

[ERROR] failed to load plugin .../Homes.dll ...
[WARN] reload of .../Homes.dll failed (code -3); keeping the previous version

A plugin folder copied into plugins/ while the server runs is picked up without a restart. The plugin worker has to grant a new plugin its data folder before the plugin can load, so the server then restarts the worker: every plugin in it is disabled and loaded again, and plugin fields are reset as they are on any reload.

/plugins shows the state of the plugin worker. /plugins reload restarts it on demand; it needs operator level 4.

Log.Debug("..."); // visible when the server runs with --verbose
Log.Info($"...");

Plugin log lines carry the plugin name: [Homes] ....

Do not use a server people play on. Start a throw-away one on a free port and bind loopback only, which is the default:

run/server.properties
server-ip=127.0.0.1
server-port=25640
online-mode=false
level-type=flat # instant superflat world, nothing saved: ideal for plugin tests
view-distance=4
simulation-distance=4

level-type=flat skips world generation and persistence. Startup then spends its time loading the datapack and recipes, which takes anywhere from seconds to a couple of minutes depending on the machine.

To get a player into the server without a Minecraft client, use the repository’s headless load-test client; it speaks the real protocol:

Terminal window
build/bin/botswarm --port 25640 --bots 1 --duration 20 --behaviour chat:100 --chat-interval 5

The bot joins, so your PlayerJoinEvent handler runs, and chat bots send chat messages, which exercises PlayerChatEvent.

The repository also contains an automated version of this walk-through: docs-site/scripts/verify-template.mjs instantiates the template, builds it, starts a scratch server, rebuilds the plugin to check the hot reload and joins a bot (npm run verify:template).

Type stop in the server console. On shutdown every plugin gets OnDisable in turn and its registrations are released.