143 lines
5.1 KiB
C++
143 lines
5.1 KiB
C++
#pragma once
|
|
|
|
#include "common/Module.h"
|
|
|
|
#include <atomic>
|
|
#include <condition_variable>
|
|
#include <cstdint>
|
|
#include <deque>
|
|
#include <map>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
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
|
|
* <url>/manifest and downloads changed files via <url>/raw/<path> 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:<reason>". */
|
|
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<RemoteFile> &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<RemoteFile> &manifest);
|
|
|
|
/** Load/persist the local sync record (.eve-manifest.json) under the hot dir. */
|
|
std::map<std::string, std::pair<int64_t, int64_t>> loadRecord() const;
|
|
void saveRecord(const std::map<std::string, std::pair<int64_t, int64_t>> &record) const;
|
|
|
|
void syncLoop(int pollMs);
|
|
|
|
void queueChange(std::string path);
|
|
|
|
std::unordered_map<std::string, std::string> bindings_; // norm path → kind
|
|
|
|
// Remote sync state (guarded by syncMu_).
|
|
mutable std::mutex syncMu_;
|
|
std::thread syncThread_;
|
|
std::condition_variable syncCv_;
|
|
std::atomic<bool> syncRunning_{false};
|
|
bool syncStarted_ = false;
|
|
bool hotDirMounted_ = false;
|
|
std::string syncUrl_;
|
|
mutable std::string syncStatus_ = "idle";
|
|
mutable std::deque<std::string> changedQueue_;
|
|
std::string hotDir_;
|
|
};
|
|
|
|
} // namespace eve::filesystem
|