[Feature]: Native headless / dedicated server mode

Open
#6,047 12 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
28/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Active
Tech stack
java

Research direction

This is a project-wide decoupling goal, not a single patch: start from the related issue #3777 and from unguarded MapTool.getFrame() / MapTool.getCampaign() uses under model/ plus EventQueue.invokeLater on non-UI paths. Done means a CLI entry point that starts a server with no frame, renderer, or EDT, plus a thread-safety story for model/macros that currently assume the Swing EDT. Do not treat the reporter's prototype as a mergeable solution until that analysis exists.

Written by the indexing model from the issue text.

Description

feature
Describe the Problem

MapTool has no way to run a server without a display. Hosting a game requires a full GUI client running somewhere, which means a dedicated or always-on server needs a desktop session, and the wiki's current answer is to run the GUI under xpra or a virtual X display.

That has practical costs: you can't run a persistent game server on a headless box without a workaround, the GM's machine is a single point of failure for the session, and there's no supported way for anything other than a full Swing client to participate.

There's a deeper version of the problem too, raised on Discord by @kwvanderlinde and worth stating here: a lot of model code assumes it is running as part of a client. Calls to MapTool.getFrame() and MapTool.getCampaign() are scattered through model/ and reach into client-specific state, and the event bus is shared between the server and the host client, so messages cross the server/client boundary that were never meant to. An embedded server can be as careful as it likes and the model can still pollute either side.

The Solution I'd like

A supported headless server mode: an entry point that starts a MapTool server with no frame, no renderer and no event dispatch thread, configured from the command line, suitable for running under systemd or in a container.

This is mostly not new code. It's removing assumptions that a client exists:

  • Non-graphical code that uses EventQueue.invokeLater for its ordering guarantee rather than for anything graphical, which ties it to AWT.
  • Unguarded MapTool.getFrame() calls in model and util code, several of which silently skip real work when they fail rather than erroring.
  • Model code resolving state through MapTool.getCampaign(), i.e. through the client singleton, rather than through the campaign it belongs to.
  • Modal dialogs on code paths a server reaches, where nobody can answer.

I'd suggest this is worth tracking as a goal in its own right because it gives the decoupling work in #3777 a concrete, testable target. "Does it run headless" is a much sharper test than "is this well separated".

Alternatives that I've considered.

xpra / Xvfb (the current wiki approach). Works, but needs a virtual display and a full Swing client's worth of resources for something with no UI, and doesn't address the underlying coupling.

A separate standalone server project. There was one for 1.3b91 that is long unmaintained. Forking the protocol and model is a lot of duplicated effort and drifts immediately.

Leaving it as-is and hosting from a GM client. Fine for most games; the gap only matters for persistent servers, alternative clients, and automation.

Additional Context

I have a working prototype, which I mention as evidence that the gap is small rather than as something ready to propose. It loads a real 14MB campaign, serves assets to clients over the normal protocol, handles multiple simultaneous clients with correct broadcast fan-out, relays fog exposure, autosaves, and runs as a systemd service. Chat, token movement, campaign load/save and asset transfer all work against stock nightly clients. MTScript also evaluates with no frame, including token-context evaluation and lib:token resolution.

Getting there took about a dozen small changes, nearly all of them guards or reorderings rather than new abstractions. Notably, most were found by running code paths rather than reading them: an empty campaign exercises almost none of the problem areas, while a real campaign with lib:tokens surfaces them immediately.

One caveat I want to state plainly rather than have someone find later. @kwvanderlinde has pointed out that UI, model and macros have essentially no thread safety and are confined to a single thread by convention, which in a Swing client means the EDT. Substituting a single-threaded executor preserves ordering, but anything reachable from the model that still assumes EDT confinement would need to be transitively covered before a headless mode could be called solid. My prototype has not done that analysis, and I don't think a headless mode should ship without it.

Related: #3777

Dominant language
Java
Stars
927
Forks
286
Avg merge
3d 17h
Merged PRs (30d)
94

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from RPTools/maptool

All issues in RPTools/maptool

Similar issues

More Java issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.