[Feature]: Native headless / dedicated server mode
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 28/100
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
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.invokeLaterfor 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
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from RPTools/maptool
-
[Bug]: An initiative list with no current entry logs an error on every changePossibly taken @dwizard0 claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 1 day
-
[Maintenance]: Migrate gradle dependency submission to modern alternativePossibly taken @VelpulaGangavardhan claimed this 18 days ago. Opencode-maintenance
Difficulty 2/5 1-3 hours Newbie friendliness 77/100
RPTools/maptool#6156 · 2 comments · 1 assignee ·
Maintainers usually reply within 1 day
-
code-maintenance
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Maintainers usually reply within 1 day
-
feature
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 1 day
Similar issues
-
Cleanup: restore the interrupt flag in AiFailureAnalyzerPossibly taken @Rishankgupta08 claimed this today. Opengood first issue
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
seleniumboot/selenium-boot#258 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100
johanhaleby/occurrent#1505 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
FasterXML/jackson-databind#6443 ·
Maintainers usually reply within 1 day
-
Docs: 2 broken linksOpen
Difficulty 1/5 Under an hour Newbie friendliness 78/100
Maintainers usually reply within 1 day