Debugging
Debug factorio-rs mods with Factorio’s native LuaDebugAdapter (--dap).
Because the adapter only understands Lua paths, factorio-rs dap sits between
your editor and Factorio and remaps locations through generated sourcemaps.
Prerequisites
Section titled “Prerequisites”- Factorio 2.1+ with native
--dap/ LuaDebugAdapter (2.0.x exits withOption 'dap' does not exist) - A Factorio binary (
FACTORIO_PATH), not only the Steam protocol launcher - VS Code with the thin factorio-rs DAP
contribution (or any DAP client that can spawn
factorio-rs dapon stdio) - Build with the debug profile so
emit_sourcemapis on (default)
On Factorio 2.0.x, use FMTK
against the generated dist/**/*.lua (no Rust sourcemap proxy), or upgrade to 2.1+.
Quick start
Section titled “Quick start”Dedicated smoke crate: examples/breakpoints (gutter
lines marked BREAKPOINT, plus debugger_breakpoint!).
cd examples/breakpointsfactorio-rs build --profile debug # writes dist/**/*.lua.map.jsonEditor setup
Section titled “Editor setup”Install a debugger contribution once (from the factorio-rs repo):
VS Code - Extensions -> … -> Install from Location… ->
editors/vscode-factorio-rs-dap
Zed - Extensions -> Install Dev Extension… -> editors/zed-factorio-rs-dap
factorio-rs init writes .vscode/launch.json and .zed/debug.json.
VS Code:
{ "version": "0.2.0", "configurations": [ { "type": "factorio-rs", "request": "launch", "name": "factorio-rs DAP", "gui": true } ]}Zed (.zed/debug.json):
[ { "adapter": "factorio-rs", "label": "factorio-rs DAP", "request": "launch", "gui": true, "manifestPath": "$ZED_WORKTREE_ROOT" }]Do not run factorio-rs dap in a normal terminal and expect to “attach”
afterward - the editor spawns the proxy on stdio. Typing in that terminal
(or piping non-DAP data) fails with DAP frame missing Content-Length.
Set breakpoints in .rs sources (gutter click). Start the session
(factorio-rs DAP). The proxy translates breakpoints to generated Lua lines;
stack frames are rewritten back to Rust when a map exists.
Factorio’s --dap process is only the debug adapter. The game starts from the
editor launch request (factorioArgs: mod directory, unsafe debug API, …).
Close any other Factorio instance first (.lock), then load/create a game so
control stage code (and your breakpoints) actually run.
Sourcemaps
Section titled “Sourcemaps”| Artifact | Meaning |
|---|---|
dist/lua/**/*.lua |
Generated Lua |
dist/lua/**/*.lua.map.json |
Line table: Lua line -> Rust file/line/column |
Profile key emit_sourcemap (default on for debug, off for release):
[profiles.debug]emit_sourcemap = truedebug_level = 1
[profiles.release]emit_sourcemap = falsedebug_level = 2 also appends -- src/foo.rs:42 trailers on generated lines
(human fallback when DAP is not attached).
Prefer debug (no optimize_ir) for the best statement<->line fidelity.
factorio-rs dap deploys with a symlink and binds breakpoints to the
resolved Lua filesystem path (Factorio’s default followSymlinks: true).
Remap lines are also appended to ~/.factorio/factorio-rs-dap.log for diagnosis.
Profiling / flamegraphs
Section titled “Profiling / flamegraphs”Mark a hot function with #[factorio_rs::profile] - the lowerer wraps the body
with guarded debugadapter.start_profile / stop_profile (no-op unless Factorio
was launched with DAP):
use factorio_rs::prelude::*;
#[profile]fn hot_path() { // work to sample}
// With an event - keep `profile` closer to the function than `event`:#[event(OnSingleplayerInit)]#[profile]pub fn on_singleplayer_init() { hot_path();}Factorio’s start_profile only accepts { show_hook_events?= boolean? } -
there is no profile name. Prefer the bare attribute (empty {}).
Then:
factorio-rs dap --gui # or editor F5# trigger the profiled function in-gamefactorio-rs profile --skip-buildfactorio-rs profile waits for a dump under Factorio’s script-output, remaps
Lua locations to Rust, and writes:
*.rust.txt- remapped Chrome DevTools.cpuprofile(JSON)*.speedscope.json- speedscope sampled profile
Avoid show_hook_events = true for flamegraphs - it attributes nearly all
samples to (hook: line) instead of your functions.
See examples/benchmarking for a nested call-tree
demo.
Manual breakpoints
Section titled “Manual breakpoints”factorio_rs::debugger_breakpoint!("hit from Rust");Expands to a Lua call into FMTK’s legacy __DebugAdapter.breakpoint when that
global exists. Native Factorio 2.1 DAP does not provide it - use gutter
breakpoints (remapped through sourcemaps) to pause.
Locals display
Section titled “Locals display”With emit_sourcemap, lowered Result tables get a small metatable so the
debug adapter can show Ok(...) / Err(...) via __tostring and expand
fields through __debugchildren / debugadapter.describe_field. Option values
stay transparent (Some(x) -> x, None -> nil).
Limits
Section titled “Limits”- Factorio DAP is single-session / not multiplayer-safe
- After rustc expand, line numbers track the lowered AST (best for path-based modules and debug profile)
- Unmapped frames stay on Lua paths (honest fallback)
See also
Section titled “See also”- breakpoints example - gutter +
debugger_breakpoint! - Profiles -
emit_sourcemap,debug_level,optimize_ir - Benchmarking - microbenches with
LuaProfiler - CLI -
factorio-rs dap/factorio-rs profileflags