Add project files.
This commit is contained in:
commit
3087fcea8a
71 files changed
+8325
No files matched your search
@@ -0,0 +1,202 @@
|
||||
/* Main.cpp -- a persistent process, unlike source/examples/
|
||||
* HotReloadDemo/Host (which runs a fixed script and exits). This one
|
||||
* loads Plugin/src/LivePlugin.cpp's compiled output once, then polls
|
||||
* that file's last-write time every second: whenever it changes (i.e.
|
||||
* you rebuilt it), it reloads automatically. Run this, then edit and
|
||||
* rebuild the plugin in another terminal -- see LivePlugin.cpp's own
|
||||
* comment for exactly what to try. Stop with Ctrl+C.
|
||||
*/
|
||||
#include "hot_reload/hot_reload.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
namespace
|
||||
{
|
||||
|
||||
std::atomic<bool> g_running{true};
|
||||
|
||||
void handleSigint(int /*signal*/)
|
||||
{
|
||||
g_running = false;
|
||||
}
|
||||
|
||||
const char* eventKindName(HotReloadSymbolEvent kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case HOT_RELOAD_SYMBOL_PATCHED:
|
||||
return "PATCHED";
|
||||
case HOT_RELOAD_SYMBOL_ADDED:
|
||||
return "ADDED";
|
||||
case HOT_RELOAD_SYMBOL_REMOVED:
|
||||
return "REMOVED";
|
||||
case HOT_RELOAD_SYMBOL_SKIPPED_NOT_CODE:
|
||||
return "SKIPPED (data, not code)";
|
||||
case HOT_RELOAD_SYMBOL_SKIPPED_TOO_SMALL:
|
||||
return "SKIPPED (too small to patch)";
|
||||
case HOT_RELOAD_SYMBOL_SKIPPED_UNWRITABLE:
|
||||
return "SKIPPED (page not writable)";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
void onSymbolEvent(const HotReloadSymbolEventInfo* event, void* /*userData*/)
|
||||
{
|
||||
if (event->kind == HOT_RELOAD_SYMBOL_REMOVED)
|
||||
{
|
||||
return; // reported, with more context, by onDeletedSymbol below
|
||||
}
|
||||
std::printf(" [reload] %-24s %s\n", eventKindName(event->kind), event->name);
|
||||
}
|
||||
|
||||
int fallbackForDeletedTick(int frame)
|
||||
{
|
||||
std::printf(
|
||||
" live_tick was deleted -- this is the Host's fallback (frame %d). Bring it back under the same "
|
||||
"name and rebuild to try LivePlugin.cpp's suggestion #2 through to the end.\n",
|
||||
frame);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void* onDeletedSymbol(const char* symbolName, void* /*userData*/)
|
||||
{
|
||||
std::printf(" [reload] REMOVED %s -> falling back to the Host's stub\n", symbolName);
|
||||
return reinterpret_cast<void*>(&fallbackForDeletedTick);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
// Force line buffering regardless of whether stdout is a real
|
||||
// terminal: this is a "watch it happen live" demo, and glibc (and
|
||||
// most libc implementations) fully buffer stdout instead of
|
||||
// line-buffering it the moment it's not connected to a TTY -- a
|
||||
// redirect to a log file, a `| tee`, or some IDEs' embedded
|
||||
// terminals all trigger this. Without this call, output can sit
|
||||
// in the buffer for a long time (observed directly while building
|
||||
// this: zero output appeared for several seconds of real,
|
||||
// correctly-running execution until the buffer was force-flushed).
|
||||
std::setvbuf(stdout, nullptr, _IOLBF, 4096);
|
||||
|
||||
std::string pluginPath = argc > 1 ? argv[1] : LIVE_PLUGIN_PATH;
|
||||
|
||||
std::printf("================================================================\n");
|
||||
std::printf(" Hot Reload -- live demo\n");
|
||||
std::printf("================================================================\n");
|
||||
std::printf(" Watching: %s\n", pluginPath.c_str());
|
||||
std::printf(" Edit: source/examples/HotReloadLiveDemo/Plugin/src/LivePlugin.cpp\n");
|
||||
std::printf(" Rebuild: cmake --build <your build dir> --target hotreload_live_plugin\n");
|
||||
std::printf(" (or run watch.sh, in this example's own directory, in another\n");
|
||||
std::printf(" terminal to rebuild automatically every time you save)\n");
|
||||
std::printf(" Stop: Ctrl+C\n");
|
||||
std::printf("================================================================\n\n");
|
||||
|
||||
std::signal(SIGINT, &handleSigint);
|
||||
|
||||
HotReloadOptions options{};
|
||||
options.on_symbol_event = &onSymbolEvent;
|
||||
options.on_deleted_symbol = &onDeletedSymbol;
|
||||
|
||||
HotReloadContext* ctx = hot_reload_context_create(&options);
|
||||
if (ctx == nullptr)
|
||||
{
|
||||
std::fprintf(stderr, "hot_reload_context_create failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (hot_reload_load(ctx, "live", pluginPath.c_str()) != HOT_RELOAD_OK)
|
||||
{
|
||||
std::fprintf(stderr,
|
||||
"Could not load '%s': %s\n"
|
||||
"(if the file exists but this still fails, that's usually a genuinely undefined symbol in the "
|
||||
"plugin -- e.g. a non-inline static class member that's declared but never defined, only caught "
|
||||
"once something actually calls into the code path that uses it; the message above names it)\n",
|
||||
pluginPath.c_str(), hot_reload_last_error(ctx));
|
||||
hot_reload_context_destroy(ctx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Resolved ONCE, here, and deliberately never re-resolved below --
|
||||
// the whole point of this demo is watching this same pointer keep
|
||||
// working, correctly, across as many reloads as you make. The one
|
||||
// case where that stops being true: if you delete live_tick (see
|
||||
// LivePlugin.cpp suggestion #2) and later re-add it under the same
|
||||
// name, the re-added one is a NEW, unrelated symbol as far as
|
||||
// diffing is concerned -- there is no "old" address to patch for
|
||||
// it, so it comes back as HOT_RELOAD_SYMBOL_ADDED, not PATCHED, and
|
||||
// this specific pointer (already redirected to the fallback below)
|
||||
// keeps running that fallback rather than picking the new one back
|
||||
// up automatically. Restart the Host to resolve it fresh, or call
|
||||
// hot_reload_get_symbol() again yourself, if you want to see that.
|
||||
using TickFunction = int (*)(int);
|
||||
auto tick = reinterpret_cast<TickFunction>(hot_reload_get_symbol(ctx, "live", "live_tick"));
|
||||
|
||||
std::error_code timeStatus;
|
||||
auto lastWriteTime = std::filesystem::last_write_time(pluginPath, timeStatus);
|
||||
|
||||
int frame = 0;
|
||||
while (g_running)
|
||||
{
|
||||
std::error_code currentTimeStatus;
|
||||
auto currentWriteTime = std::filesystem::last_write_time(pluginPath, currentTimeStatus);
|
||||
if (!currentTimeStatus && currentWriteTime != lastWriteTime)
|
||||
{
|
||||
// A short settle delay for a linker that might still be
|
||||
// mid-write on a slower filesystem -- not load-bearing for
|
||||
// correctness either way: a reload attempted too early
|
||||
// just fails cleanly (hot_reload_reload() never disturbs
|
||||
// the running version on failure) and gets retried next
|
||||
// iteration, since lastWriteTime below is only updated on
|
||||
// success.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(150));
|
||||
|
||||
std::printf("\n-- change detected in %s, reloading --\n", pluginPath.c_str());
|
||||
HotReloadStatus status = hot_reload_reload(ctx, "live", pluginPath.c_str());
|
||||
if (status == HOT_RELOAD_OK)
|
||||
{
|
||||
lastWriteTime = currentWriteTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::printf("-- reload failed: %s -- still running the previous version; fix and save again --\n",
|
||||
hot_reload_last_error(ctx));
|
||||
}
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
if (tick != nullptr)
|
||||
{
|
||||
tick(frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::printf(" (live_tick not found -- build the plugin; see instructions above)\n");
|
||||
}
|
||||
|
||||
// Unlike tick above, deliberately re-resolved every frame:
|
||||
// live_extra is not expected to exist until you add it (see
|
||||
// LivePlugin.cpp suggestion #3), so the Host has to keep
|
||||
// checking rather than assume it already knows the answer.
|
||||
// This is a cheap lookup over a handful of symbols, not a
|
||||
// reload -- fine to do every frame.
|
||||
using ExtraFunction = int (*)(int);
|
||||
auto extra = reinterpret_cast<ExtraFunction>(hot_reload_get_symbol(ctx, "live", "live_extra"));
|
||||
if (extra != nullptr)
|
||||
{
|
||||
extra(frame);
|
||||
}
|
||||
|
||||
++frame;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
}
|
||||
|
||||
std::printf("\nShutting down.\n");
|
||||
hot_reload_context_destroy(ctx);
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user