Current release only. Previous releases are split per major line under
changelog/ — see changelog/index.md
for the full directory.
Compared to v2.2.0 (2026/03/15).
Headline: native Open Multiplayer component ABI implemented in pure Rust (Itanium and MSVC); a single binary now works as a SA-MP plugin and as a first-class Open Multiplayer component, with no extra configuration.
This release contains breaking changes. A plugin written against v2.x will not compile against v3.0.0 without edits. Minimum diff:
| Before (v2.x / upstream) | After (v3.0.0) |
|---|---|
fn process_tick(&mut self) { … } |
fn on_tick(&mut self, _ctx: TickContext) { … } |
samp::plugin::enable_process_tick() |
samp::plugin::enable_tick() (or enable_tick_with(TickConfig)) |
samp::cell::string::put_in_buffer(buf, s)? |
buf.write_str(s)? / unsized.write_str(size, s)? (put_in_buffer is pub(crate) now) |
samp::raw::functions::Logprintf (variadic) |
Same path, now extern "C" fn(*const i8) — the SDK formats in Rust and passes a single C string |
example-hello/ / example-counter/ / plugin-example/ |
examples/hello/ / examples/counter/ / examples/advanced/ |
Open Multiplayer support comes turned on by default — the build
produces both the SA-MP exports and the ComponentEntryPoint. To
keep the v2.x behavior unchanged, enable the new samp-only feature:
samp = { git = "...", tag = "v3.0.0", features = ["samp-only"] }Two new requirements that affect builds:
- The workspace's
[profile.release]addslto = "thin",codegen-units = 1,strip = true. Override in your ownCargo.tomlif you need otherwise. - The i686 target is now strictly required at compile time
(
OmpComponenthasconst _layout asserts). Settarget = "i686-unknown-linux-gnu"in.cargo/config.tomlif you were relying on the host target picking up automatically.
The full step-by-step walkthrough lives in
docs/migration.md — including the new
TickConfig knobs (sa_mp_only(), omp_only(Duration), custom
omp_interval) and how to choose between them.
samp: 2.2.0 → 3.0.0samp-sdk: 2.2.0 → 3.0.0samp-codegen: 1.2.0 → 1.3.0
SampPlugin::process_tickreplaced bySampPlugin::on_tick(&mut self, ctx: TickContext)— unified callback that fires on both servers. Cadence is the server's main loop on SA-MP and the SDK-ownedITimersComponenttimer on native Open Multiplayer (interval configurable).TickContext::sourcereports the origin (TickSource::SaMp/TickSource::OmpTimer);TickContext::elapsedis the wall-clock interval since the previous dispatch.samp::plugin::enable_process_tickreplaced bysamp::plugin::enable_tick()(default config) andsamp::plugin::enable_tick_with(config: TickConfig)(explicit per-server toggle + Open Multiplayer interval).samp::cell::string::put_in_bufferis nowpub(crate)(waspub). The public API for writing strings isBuffer::write_strandUnsizedBuffer::write_str.samp_sdk::raw::functions::Logprintfsignature changed from variadicextern "C" fn(*const i8, ...)to fixed-arityextern "C" fn(*const i8)— the SDK formats the message in Rust and passes a single C string, matching whatlogprintf("%s", msg)does at the ABI level.- Example crates renamed:
example-hello/→examples/hello/,example-counter/→examples/counter/,plugin-example/→examples/advanced/. Workspace members updated accordingly.
samp_sdk::omp— new top-level module with eight submodules:component—OmpComponent,IComponentVTable,IUIDProviderVTable, opaque types (ICore,IComponentList,ILogger,IEarlyConfig), default vtable implementations for both ABIs.component_api—OmpComponentHandletrait, genericcomponent_name<T>()/component_version<T>()helpers.core—LogLevel,core_print_ln,core_log_ln,core_print_ln_u8,core_log_ln_u8.events—PawnEventHandler,PawnEventHandlerVTable.server—PAWN_COMPONENT_UID,NUM_AMX_FUNCS,PawnComponent,ServerComponentList,ServerComponent,ServerPawnComponent,IEventDispatcherPawn,IPawnScript,AmxFunctionTable, plus the free functionsquery_component,add_pawn_event_handler,remove_pawn_event_handler,get_pawn_event_dispatcher,get_amx_from_script,get_amx_functions.timers—TIMERS_COMPONENT_UID,TimersComponent,ITimersComponent,ITimer,TimerHandlerVTable,TimerTimeOutHandler,create_repeating_timer,kill_timer,query_timers_component.types—UID,SemanticVersion(withnewandwith_prerel),StringView(withas_str,try_as_str,from_static),Colour(withrgb,rgba,from_rgba_u32,to_rgba_u32and theWHITE,BLACK,NONEconstants),Vector2,Vector3,Vector4,ComponentType.vtable—subobject_ptr,vtable_slot,secondary_call_targethelpers for safe access to secondary vtables.
samp::ompre-exports the module above.samp::pluginnew functions:enable_tick,enable_tick_with,omp_core,omp_query_component,omp_query::<T>(typed wrapper forOmpComponentHandleimplementors).samp::pluginnew types:TickConfig,TickContext,TickSource.SampPluginnew hooks:on_tick(ctx),on_omp_ready(gated bynot(feature = "samp-only")),on_component_free(same gating).samp::logre-export —#[native]-expanded code now usessamp::log::error!, so user crates no longer need to declarelogas a direct dependency just to satisfy the macro.
- Optional metadata fields:
uid: <u64 expression>,component_name: "...",component_version: (x, y, z). samp-codegenreads[package.metadata.samp]from the project'sCargo.toml. Resolution order per field: macro argument >[package.metadata.samp]> derived value (CARGO_PKG_NAME, parsedCARGO_PKG_VERSION, FNV-1a 64 ofCARGO_PKG_NAME@CARGO_PKG_VERSIONfor the UID).- When the UID is missing from both sources, the generated value is
persisted back into
Cargo.tomlunder[package.metadata.samp]so subsequent builds reuse the same identifier. - Generates the SA-MP exports (
Load,Unload,Supports,AmxLoad,AmxUnload,ProcessTick) and the Open MultiplayerComponentEntryPointby default. Opt out with thesamp-onlyfeature.
- Accepts associated functions (no
self), in addition to methods. - Return type detection:
Result/AmxResultis matched againstOk/Err; any other type implementingAmxCellis used as the return cell directly (no spuriousOk(...)wrapping). - Accepts
&AmxString(and any other&T) parameters — the macro materializes the owned value fromargs.next_arg()and injects&localat the call site. - Validates the
name = "..."literal at proc-macro time: interior\0bytes now produce a compile error instead of panicking atCString::newduring server load. - Wraps every invocation in
std::panic::catch_unwind. Panics that would otherwise cross theextern "C"boundary (process abort on Rust 1.71+) are caught, logged as[<NativeName>] panic in native: <payload>, and converted to a0return. - Argument parsing failures now log
[<NativeName>] failed to parse argument #<i> '<name>' (expected type: <Type>)— both the positional index and the expected type are included.
- New
samp-onlyfeature on bothsampandsamp-sdk: removes every Open Multiplayer code path. The plugin still loads on Open Multiplayer, but in legacy mode (no component API). - New workspace
[profile.release]:lto = "thin",codegen-units = 1,strip = true. Cargo.lockis now committed (removed from.gitignore).Cargo.tomlper crate exposespackage.metadata.docs.rs.default-target = "i686-pc-windows-msvc"features = ["encoding"]so docs.rs builds with the right target.
- New build scripts:
scripts/build-linux.sh— produces.so(i686-unknown-linux-gnu) and.dll(i686-pc-windows-msvcviacargo-xwin --xwin-arch x86, ori686-pc-windows-gnuwith--samp-only).scripts/build-windows.sh— produces.dllnatively and.sothrough WSL or Docker/cross (autodetected; forceable with--wsl/--docker).
- New helper scripts used by the benchmark workflow:
scripts/append-bench-history.py,scripts/extract-bench.py,scripts/render-bench-entry.py. - New GitHub workflows:
docs.yml(publishes the MkDocs site),release.yml(creates releases onv*tags, attaches a source tarball with only the essential crates),release-drafter.yml,labels.yml,bench-release.yml(per-release benchmark history on thebench-databranch). .github/labels.ymland.github/release-drafter.ymlfor the workflows above.rust.ymlworkflow: action versions bumped toactions/checkout@v6,actions/upload-artifact@v7,actions/cache/restore@v5,actions/cache/save@v5(Node 24 baseline); benchmark job restricted to-p samp-sdkto avoidUnrecognized option: 'save-baseline'; artefact retention now capped at 14 days.
- Unit tests grew from 80 (v2.2.0) to 207 (this release) — +127 tests.
- New per-module test files:
samp-sdk/src/tests/amx_cell.rs(10 tests).samp-sdk/src/tests/amx_string.rs(8 tests).samp-sdk/src/tests/buffer.rs(12 tests).samp-sdk/src/tests/omp_lifecycle.rs(4 tests).
- Inline coverage added to every new
ompsubmodule (component,component_api,core,events,server,timers,types,vtable) — 62 tests across them. samp-codegengained 26 unit tests inplugin.rscoveringfnv1a_64,parse_uid_str,parse_version_str, andread_samp_metadata_from_content.- New
samp-sdk/benches/buffer_bench.rs(Criterion):get_as::<f32>,set_as::<bool>,iter_as::<i32>,iter_as::<f32>at sizes 8 / 64 / 256 / 1024. samp-sdk/benches/string_bench.rsreworked: usesstd::hint::black_box(prevents LLVM DCE) and exercisesBuffer::write_str/UnsizedBuffer::write_stralongside the existing baselines.
examples/hello/src/lib.rsandexamples/counter/src/lib.rsship with full source (previously only hadCargo.tomlplaceholders).examples/README.mdplus oneREADME.mdper example (hello,counter,advanced) documenting the natives, the patterns demonstrated, and how to build each one in isolation.
- All SDK diagnostic warnings are routed through the standard
logfacade with the[rust-samp]prefix. New warnings cover the Open Multiplayer lifecycle (nullICore*inon_load, missingIPawnComponentinon_init, nullIEventDispatcher,getAmxFunctions()returning 0 inon_ready, missingITimersComponentwhen the tick is enabled). - Default log routing now writes via
ICore::logLnU8when the plugin runs on native Open Multiplayer, mappinglog::Leveltosamp_sdk::omp::LogLevelautomatically; SA-MP behavior is unchanged (logprintf). f32::as_cellusesf32::to_bits(*self).cast_signed()(Rust 1.87+ helper) instead of anas i32round trip.Allocator::string_byteslifetime simplified to&str → Cow<'_, [u8]>.Args::newparameter renamedargs→params(positional API unchanged).- Compile-time layout assertions for
OmpComponent:- Linux i686 (gated by
target_os = "linux"):offset_of!(uid_vtable) == 40,size_of == 56. - Windows MSVC i686 (gated by
target_env = "msvc"):offset_of!(uid_vtable) == 56. - Both with explanatory error messages on mismatch.
- Linux i686 (gated by
- Open Multiplayer adaptive bootstrap:
getAmxFunctions()is tried inon_initand the pointer is stored if non-zero; otherwise the SDK retries inon_ready. This works for the current Open Multiplayer release (1.5.x — returns 0 inon_init) and any future release that populates the table earlier, without code changes. - AMX scripts that arrive via
on_amx_loadbefore the AMX function table is available are now queued and processed inon_readyinstead of being dropped. omp_cleanupcorrectly kills the tick timer (if any) and removes thePawnEventHandlerfrom the dispatcher before the component is unloaded, preventing use-after-free if the server fires Pawn events during shutdown.- The native-name
CStringallocation is leaked throughBox::leak(CString::into_boxed_c_str())and the leak is now documented at the leak site (was previously implicit viaCString::into_raw()). - All compiler-emitted error messages from
samp-codegenare now in English (were a mix of English and Portuguese).
- README,
samp-sdk/readme.md,samp-codegen/readme.md, and rootmigration.mdrewritten in English. - mdBook removed (
docs/book.toml, everydocs/src/*page) and replaced by a MkDocs Material site underdocs/. New pages:introduction,setup,first-plugin,plugin-anatomy,natives,amx-types,cells-and-memory,encoding,error-handling,logging,advanced-examples,api-reference,omp-native,migration, plus the newexec-public,build-scripts,diagnostics, andinternals/omp-abi. - All source-code docstrings translated from Portuguese to English (in-tree only — user-facing release notes and prose stay in English as well).
- This
CHANGELOG.mdnow only carries the current release; older releases moved tochangelog/v1.x.md,changelog/v2.x.md, andchangelog/historical.md(pre-forksamp-rs).
- Dev dependency
criterionbumped 0.5 → 0.8. - No other runtime dependency changes.
| Target | SA-MP | Native Open Multiplayer | Notes |
|---|---|---|---|
i686-unknown-linux-gnu |
✅ | ✅ (Itanium ABI) | Default on Linux. |
i686-pc-windows-msvc |
✅ | ✅ (MSVC ABI) | New in 3.0.0. Cross-compile from Linux via cargo xwin build --xwin-arch x86. |
i686-pc-windows-gnu |
✅ | ❌ | Use with --features samp-only. |
.gitignore:Cargo.lockremoved (now committed); addeddist/(build-script artefacts),site/(MkDocs build),bench-entry.json,bench-history.json,bench_report.md,bench_results.txt,bench_comparison.txt,__pycache__/,*.pyc,release_body.md(release workflow tempfile),rust-samp-*-src.tar.gz(local release tarball name), and common editor / OS junk (*.swp,*.swo,.DS_Store,Thumbs.db); consolidatedtargetignore.- New
.gitattributeswithexport-ignorerules so docs, examples, scripts,.github/,changelog/,notes/,mkdocs.yml,ROADMAP.md,CHANGELOG.md, andmigration.mdare excluded from the auto-generated "Source code (zip/tar.gz)" archives GitHub attaches to every release/tag. Also normalizes line endings (text=auto eol=lf) so shell scripts survive Windows checkouts and tags common binary extensions explicitly. release.ymlhardened: thetarstep adds defensive--excludeflags (target,site,dist,__pycache__,*.pyc,*.rs.bk,.DS_Store,Thumbs.db,.git*,*.swp,*.swo) and a new verification step fails the release if any forbidden path slips into the SDK source tarball.- Workspace members updated to the renamed example crates.
- Per-crate
authorsfield normalized to"ZOTTCE <zottce@gmail.com>", "NullSablex <https://github.com/NullSablex>".