#pragma once #include "common/Module.h" #include #include #include #include #include #include #include #include #include #include namespace eve::filesystem { /** * @brief Path→reload dispatcher for soft hot reload. * Driven from load.nut via pollWatch → tryReload; also used by watchTree. * * What a given file means is not known here. Modules that own an asset kind * register an eve::caps::IAssetReloader (see common/AssetReloader.h) and this * only routes paths to them, so reloadable kinds grow with the linked module * set rather than with this file. * * On top of the local watchers, HotReload can act as a remote hot-reload client: * a background thread polls a dev server (`eve dev`) manifest, downloads changed * files into a writable overlay directory mounted at "/" (see * Filesystem::mountRealDirectory), and queues the changed virtual paths for the * script layer to `dofile` / reload. This is what makes hot reload work on * read-only mobile bundles (iOS app bundle / Android APK). */ class HotReload : public Module { public: Module_REG(HotReload); HotReload() = default; ~HotReload() override; /** * @brief Pin a path to one reloader kind ("particle" / "tilemap" / "texture" / * whatever a linked module registers), bypassing extension matching. * "auto" (the default) lets every reloader claim the path itself. */ void bind(std::string path, std::string kind = "auto"); void unbind(std::string path); /** @brief Offer a (normalized) path to the registered reloaders; true if any reloaded. */ bool tryReload(std::string path); /** @brief Recursively watch root and all subdirectories. Returns number of watches added. */ int watchTree(std::string root = "."); /** * @brief If path is a directory, recursively add watches for it and its current descendants. * @return True when path names a directory, including one that was already watched. */ bool watchNewDirectory(std::string path); /** * Start remote hot reload against a dev server. Background thread polls * /manifest and downloads changed files via /raw/ into a * writable overlay directory (mounted in front of the bundled game source), * then queues the virtual paths. Returns false if already running. * @param url Base URL, e.g. "http://192.168.1.5:8765". * @param pollMs Poll interval in milliseconds (default 1000). */ bool startRemoteSync(std::string url, int pollMs = 1000); /** Stop the remote sync thread (joining it). Safe to call twice. */ void stopRemoteSync(); /** * Override the writable overlay directory used to stage remote-sync * downloads (default: platform appdata/EVE/hotreload, or the internal * storage hotreload dir on Android). Must be called before startRemoteSync. */ void setRemoteHotDir(std::string dir); /** Whether the remote sync thread is currently running. */ bool isRemoteSyncing() const; /** Last sync status: "idle" | "syncing" | "synced" | "error:". */ std::string remoteSyncStatus() const; /** * Pop the next changed virtual path reported by remote sync. * Returns empty string when the queue is drained. */ std::string pollRemoteChange(); static std::string normalizePath(std::string path); private: struct RemoteFile { std::string path; int64_t size = -1; int64_t mtime = -1; }; // --- Remote sync internals --- /** Resolve + create the writable overlay directory for downloaded files. */ std::string ensureHotDir(); /** Mount the overlay directory in front of "/" so synced files shadow the bundle. */ bool mountHotDir(); /** Fetch + parse the server manifest. Returns false on any failure. */ bool fetchManifest(std::vector &out); /** Download one file from the server into the overlay dir. */ bool downloadFile(const std::string &relPath); /** Compare manifest against the local record; download/add/remove as needed. */ void applyManifest(const std::vector &manifest); /** Load/persist the local sync record (.eve-manifest.json) under the hot dir. */ std::map> loadRecord() const; void saveRecord(const std::map> &record) const; void syncLoop(int pollMs); void queueChange(std::string path); std::unordered_map bindings_; // norm path → kind // Remote sync state (guarded by syncMu_). mutable std::mutex syncMu_; std::thread syncThread_; std::condition_variable syncCv_; std::atomic syncRunning_{false}; bool syncStarted_ = false; bool hotDirMounted_ = false; std::string syncUrl_; mutable std::string syncStatus_ = "idle"; mutable std::deque changedQueue_; std::string hotDir_; }; } // namespace eve::filesystem