no-unsafe-natives
This page is generated from packages/eslint-plugin-reforged/docs/no-unsafe-natives.md by docs:collect. Edit the source, not this page.
Reports a call to a Native on the plugin's ban list: the ones that kill the thread, leak or desync (TriggerSleepAction, PolledWait, TriggerWaitForSound, DestroyEffectAfterTimeBJ, the BJ timer helpers, SelectGroupForPlayerBJ, SmartCameraPanBJ). An error in the recommended config; the message gives the reason and the replacement.
Why
Pitfalls C2, S1 and D9 of the catalogue (#15):
- C2, sleeping Natives in the wrong context.
TriggerSleepAction"works only in a trigger action execution context, not in trigger conditions nor for example in timer functions orForGroupfunctions ... If this is called in the wrong context, it crashes the thread" (jassdoc,@bug).PolledWaitwaits through it.TriggerWaitForSoundis the same kind of wait: common.j declares it besideTriggerSleepActionin the trigger action API, and it yields the thread until the sound ends. In 3.0.0,DestroyEffectAfterTimeBJstores its arguments inbj_destroyEffectAsyncEffect/Timeand callsExecuteFunc("DestroyEffectAsyncBJ"), which sleeps withTriggerSleepActionbefore destroying the effect (Blizzard.j of Patch 3.0.0.24268; Warcraft III 3.0 Bugs & issues, post #36). ReplacingTriggerSleepActionloops with timers fixed LAN drops in a converted map (map desyncs). - S1, BJ globals reused between calls.
CreateTimerBJ,StartTimerBJandGetLastCreatedTimerBJsharebj_lastStartedTimer, which Blizzard.j creates in the Lua root and every call overwrites (A comprehensive guide to mapping in Lua);StartTimerBJis also listed in Known causes of desync. - D9, async-by-design engine behaviour.
SelectGroupForPlayerBJrunsForGroupinside aGetLocalPlayerbranch, a "systematic desync" on 1.32 and later;SmartCameraPanBJis listed as a desync cause, reported fixed in 1.31 (Known causes of desync).
The ban list is data/unsafe-natives.json in the package: one entry per Native with its reason and replacement, grown by pull request.
Incorrect
import { Timer } from "reforged-ts";
Timer.after(1, () => {
TriggerSleepAction(2); // kills this thread without an error
print("never printed");
});
const timer = CreateTimerBJ(false, 5); // no callback, shared global
Correct
import { Timer } from "reforged-ts";
Timer.after(1, () => {
Timer.after(2, () => {
print("printed three seconds after start");
});
});
const timer = Timer.create();
timer.start(5, false, () => {
print("expired");
});
Options
allow (array of Native names, default []): ban-list entries this project allows everywhere.
{ rules: { "reforged/no-unsafe-natives": ["error", { allow: ["TriggerSleepAction"] }] } }
Suggestions and fixes
None: each replacement moves code into a callback, which changes when it runs.
When not to use it
When a call runs where the ban's reason does not apply, such as TriggerSleepAction in a trigger action. Silence that one line and say why:
// eslint-disable-next-line reforged/no-unsafe-natives -- runs in a trigger action, where the sleep is safe
TriggerSleepAction(1);