FyroxEngine/Fyrox · error

Unable to reload dynamic plugins. Reason

Error message

Unable to reload dynamic plugins. Reason: {message}

What it means

Fyrox logs this when `reload_dynamic_plugins` fails during hot-reloading of dynamic plugin libraries. The plugin .so/.dll could not be reloaded (compile error, file lock, missing export symbols), so the previously loaded plugin version stays active and hot reload is skipped for that iteration.

Solutions

  1. Read the logged `{message}` — it names the specific load failure (missing file, bad library, missing symbols)
  2. Wait for the build to fully finish before reload (add a debounce or wait on cargo's exit)
  3. On Windows, copy the built plugin to a new filename and load that, avoiding locks on the original
  4. Rebuild the plugin and verify it exports the required `fyrox::plugin::PluginConstructor` entry points
  5. Ensure the dynamic-plugins feature and matching fyrox/fyrox-core versions are used for plugin and host

Example fix

// before: reloading while cargo still writing the dylib
plugin_watcher.on_change(|_| engine.update_hot_reload());

// after: debounce until the build output stabilizes
plugin_watcher.on_change_debounced(Duration::from_millis(500), |_| engine.update_hot_reload());
Defensive patterns

Strategy: retry

Validate before calling

fn plugin_dylib_ready(path: &Path) -> bool {
    match std::fs::metadata(path) {
        Ok(m) => m.len() > 0
            && m.modified().ok().map(|t| t.elapsed().unwrap_or_default()).unwrap_or_default()
                > std::time::Duration::from_millis(250),
        Err(_) => false,
    }
}

Try / catch

// Retry the reload on the next tick after a failure
if let Err(message) = engine.update_hot_reload() {
    log::warn!("plugin reload deferred: {message}");
    schedule_retry(Duration::from_secs(1));
}

Prevention

When it happens

Trigger: Running with dynamic-plugins hot reloading enabled and calling the engine's `update_hot_reload`/plugin-reload path with dynamic libraries: the plugin .dll/.so is still being written by the compiler, is locked by the OS, was deleted, or fails to load/link.

Common situations: Cargo/rustc has not finished emitting the new plugin binary when the watcher fires; Windows file locks on the dll; plugin renamed or output path changed in Cargo.toml; deploying a plugin built for a different ABI/compiler version.

Related errors


AI-assisted analysis of FyroxEngine/Fyrox@76c91aad8e (2026-09-10). Data as JSON: /api/errors/9a18b5fa115b14ae. Report an issue: GitHub.

Appendix: source

Thrown at fyrox-impl/src/engine/mod.rs:1665

    ///
    /// ## Platform-specific
    ///
    /// - Windows, Unix-like systems (Linux, macOS, FreeBSD, etc) - fully supported.
    /// - WebAssembly - not supported
    /// - Android - not supported
    pub fn handle_plugins_hot_reloading<F>(
        &mut self,
        #[allow(unused_variables)] dt: f32,
        #[allow(unused_variables)] controller: ApplicationLoopController,
        #[allow(unused_variables)] lag: &mut f32,
        #[allow(unused_variables)] on_reloaded: F,
    ) where
        F: FnMut(&dyn Plugin),
    {
        #[cfg(any(unix, windows))]
        {
            if let Err(message) = self.reload_dynamic_plugins(dt, controller, lag, on_reloaded) {
                Log::err(format!(
                    "Unable to reload dynamic plugins. Reason: {message}"
                ))
            }
        }
    }

    /// Performs pre update for the engine.
    ///
    /// Normally, this is called from `Engine::update()`.
    /// You should only call this manually if you don't use that method.
    ///
    /// ## Parameters
    ///
    /// `lag` - is a reference to time accumulator, that holds remaining amount of time that should be used
    /// to update a plugin. A caller splits `lag` into multiple sub-steps using `dt` and thus stabilizes
    /// update rate. The main use of this variable, is to be able to reset `lag` when you doing some heavy
    /// calculations in a your game loop (i.e. loading a new level) so the engine won't try to "catch up" with
    /// all the time that was spent in heavy calculation. The engine does **not** use this variable itself,

View on GitHub (pinned to 76c91aad8e)