# Gum Service

> GumService Reference

- Skill: `vchelaru/gum-service` (Agent Skill)
- Install (CLI): `npx skillmds@latest add vchelaru/gum-service`
- Raw SKILL.md: https://api.skillmd.com/api/skills/vchelaru/gum-service/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: vchelaru (https://skillmd.com/u/vchelaru)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/vchelaru/gum-service

---


# GumService Reference

## What It Is

`GumService` is the runtime-facing API that game developers use to initialize, update, and draw Gum UI. It lives in `MonoGameGum/GumService.cs` (compiled for XNALIKE, RAYLIB via `#if`) under the `Gum` namespace (since issue #3119 / syntax version 3). Legacy `MonoGameGum.GumService` / `RaylibGum.GumService` names are permanent `[Obsolete]` subclass shims in `MonoGameGum/GumServiceCompat.cs` (linked into `RaylibGum.csproj`). `WindowZoomMode`, `GumHotReloadManager`, and related hot-reload types also live in `namespace Gum`.

**The soft migration is intentional and finished — do not flag it as incomplete.** What matters is what the **user types**: new code uses `using Gum;` and types the service as `GumService` (= `Gum.GumService`) with no `using MonoGameGum;` / `using RaylibGum;` — that is 100% future-proof. `Gum.GumService.Default` is *deliberately* typed as the derived `[Obsolete]` shim so legacy `using MonoGameGum;` / `using RaylibGum;` declarations keep compiling; the user never names the obsolete type, so the modern path is warning-free. Do **not** propose "completing" this by retyping `Default` to `Gum.GumService` or removing the shims — that would break back-compat and is not planned (stable for the foreseeable future). The fact that `Default`'s declared type is an obsolete shim is not a problem and must not be raised as a follow-up.

**Not the CLI.** `Gum.Cli` / `Gum.ProjectServices` are separate tools for headless project validation and codegen.

## Lifecycle

```
GumService.Default.Initialize(game)   // one-time setup
  ↓ per frame:
GumService.Default.Update(gameTime)
GumService.Default.Draw()
  ↓ on teardown (optional):
GumService.Default.Uninitialize()
```

`Initialize` throws if called twice without an intervening `Uninitialize`.

`Draw()` draws everything through `SystemManagers.Default` in one call — for manual control over what's drawn when/to-which-target (e.g. compositing onto multiple `RenderTarget2D`s in one frame), use `GumBatch`'s immediate-mode `Begin`/`Draw`/`End` instead (`docs/code/rendering/gumbatch.md`; internals in the **gum-monogame-rendering** skill).

**Only the `gumProjectFile` overload loads a project.** `Initialize(game, gumProjectFile)` loads the `.gumx` and runs the project-load path — which applies project-level settings (standard-element defaults, localization, and the project's `TextureFilter` → `Renderer.TextureFilter`, issue #3199). The `Initialize(game, DefaultVisualsVersion)` / parameterless overloads are **code-only** and never touch that path. So when manually verifying anything that depends on a loaded project, you must pass the `.gumx` path or the behavior under test never runs. The cheapest from-file smoke test is `Initialize(game, "GumProject/GumProject.gumx")` plus a `SpriteRuntime` (`SourceFileName` set, scaled up) added to `Root`.

## Singleton Pattern

`Default` is lazily initialized via `??=`. `Uninitialize()` sets `_default = null`, so after teardown `GumService.Default` creates a **fresh instance** — any stored reference to the old instance is now orphaned.

## Uninitialize — Non-Obvious Details

`Uninitialize()` resets a large amount of shared static state across multiple singletons. Key things it does that are surprising:

- Nulls `GraphicalUiElement.SetPropertyOnRenderable`, `AddRenderableToManagers`, etc. — delegates wired by `FormsUtilities.InitializeDefaults`
- Calls `ElementSaveExtensions.ClearRegistrations()` — clears `RegisterGueInstantiation` / `RegisterDefaultInstantiationType` callbacks
- Calls `LoaderManager.Self.DisposeAndClear()` — disposes GPU content and empties the cache
- Nulls and removes `FrameworkElement.PopupRoot` and `FrameworkElement.ModalRoot` from managers
- Resets `FileManager.RelativeDirectory` to `"Content/"` (only meaningful if a project was loaded)
- Calls `_systemManagers.Renderer.Uninitialize()` (XNALIKE only)
- Sets `_default = null` — next access to `Default` creates a new `GumService`

`FormsUtilities.Uninitialize()` is `internal`; tests access it via `InternalsVisibleTo`.

## Roots

| Property | Purpose |
|----------|---------|
| `Root` | Main scene container; sized to canvas on each `Update` |
| `PopupRoot` | Overlaid above Root; for non-modal popups |
| `ModalRoot` | Topmost layer; blocks input to everything below |

`PopupRoot` and `ModalRoot` are `FrameworkElement` statics, not instance fields — they are shared across all `GumService` instances.

## Multiple GumService Instances

The constructor is public (`new GumService()`), but two instances don't give two independent UI spaces — `GraphicalUiElement.CanvasWidth`/`CanvasHeight`, `FormsUtilities`'s `Cursor`/`Keyboard`, and `SystemManagers.Default`/`ISystemManagers.Default` are static, so the second instance's `Initialize()` overwrites state the first depends on (e.g. `Cursor`'s zoom-aware hit-testing reads `SystemManagers.Default` directly, not its owning instance). Tracked as issue #4096.

## Key Files

| File | Purpose |
|------|---------|
| `MonoGameGum/GumService.cs` | Main class (`namespace Gum`) |
| `MonoGameGum/GumServiceCompat.cs` | `[Obsolete]` subclass shims for `MonoGameGum.GumService` / `RaylibGum.GumService`; also holds the `AddChild` codegen crutch and `ToGraphicalUiElement` forwarder |
| `MonoGameGum/Forms/FormsUtilities.cs` | Input/cursor/gamepad setup; `Uninitialize()` lives here |
| `GumRuntime/ElementSaveExtensions.GumRuntime.cs` | `ClearRegistrations()` called during Uninitialize |
| `RenderingLibrary/Content/LoaderManager.cs` | `DisposeAndClear()` called during Uninitialize |
| `Tests/MonoGameGum.Tests.V3/GumServiceUninitializeTests.cs` | Tests for GPU-accessible Uninitialize behavior |
| `Tests/Gum.ProjectServices.Tests/UninitializeTests.cs` | Tests for non-GPU Uninitialize behavior |

## Testing Split

Uninitialize tests are split across two projects because `FormsUtilities`, `LoaderManager`, and `ElementSaveExtensions` don't all require a GPU:

- `Gum.ProjectServices.Tests` — `LoaderManager`, `ElementSaveExtensions`, `ObjectFinder` (no GPU needed)
- `MonoGameGum.Tests.V3` — `FormsUtilities`, root containers, `FrameworkElement` statics (require test setup with a mock `SystemManagers`)

## Hot Reload

`GumService.EnableHotReload(absoluteGumxSourcePath)` wires up a `GumHotReloadManager` that watches the source project directory and rebuilds `Root.Children` when `.gumx`/`.gusx`/`.gucx`/`.gutx`/`.fnt` files change. `GumService.Update` ticks it each frame; `Uninitialize` stops and nulls it. For details on the reload pipeline, debounce, font cache eviction, and gotchas, load the **gum-runtime-hot-reload** skill. Public docs: `docs/code/hot-reload.md`.

