Added ARM32 support
This commit is contained in:
1 parent
df792729ef
commit
1212cb9dbe
36 files changed
+2381
-783
No files matched your search
@@ -0,0 +1,143 @@
|
||||
#pragma once
|
||||
|
||||
#include "hot_reload/Options.hpp"
|
||||
#include "hot_reload/Result.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
/**
|
||||
* @file Context.hpp
|
||||
* @brief hot_reload::Context -- the primary, modern C++ API this library offers.
|
||||
*
|
||||
* `hot_reload.h`'s plain C API is a separate, independently stable surface built *on top of*
|
||||
* this one (see `c-api/HotReloadApi.cpp`): it goes through Context's public methods exactly as
|
||||
* any other caller would, with no special access to this class's internals, so this header and
|
||||
* everything it includes has no idea the C API exists at all. Prefer this header for any new
|
||||
* C++ code; reach for `hot_reload.h` only where C linkage itself is the requirement (a plugin
|
||||
* boundary, a different language's FFI).
|
||||
*/
|
||||
namespace hot_reload
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Loads and hot-reloads shared libraries ("modules"), redirecting matching exported
|
||||
* functions from a module's previous build to its new one in place.
|
||||
*
|
||||
* A context owns every version of every module loaded through it. Destroying it closes all
|
||||
* of them, after which any function or data pointer obtained from any of those modules --
|
||||
* including an old, patched entry point that now only jumps into a newer version -- is
|
||||
* dangling.
|
||||
*
|
||||
* @par Thread safety
|
||||
* Safe to call load(), reload(), and findSymbol() on the same Context from multiple threads
|
||||
* at once: an internal reader-writer lock serializes load()/reload() against each other and
|
||||
* against findSymbol(), while multiple concurrent findSymbol() calls run in parallel against
|
||||
* each other. This covers this *object's own bookkeeping* -- it does not and architecturally
|
||||
* cannot cover a thread that is, at the exact moment of a reload(), already executing inside
|
||||
* the function being replaced; reload() from a quiescent point for the module involved
|
||||
* (nothing currently running inside it) the same as a single-threaded caller would. See
|
||||
* `source/HotReload/README.md`'s "What this cannot detect" for the full reasoning, including
|
||||
* exactly which write-ordering protections the underlying patch *does* carry regardless.
|
||||
* Unlike the C API's `hot_reload_last_error()`, there is no shared, stateful "last error" to
|
||||
* race on here at all: every call's outcome comes back in its own Result, so two threads'
|
||||
* calls can never be confused with each other.
|
||||
*
|
||||
* @code
|
||||
* hot_reload::Context ctx;
|
||||
* if (hot_reload::Result result = ctx.load("plugin", "plugin.so"); !result)
|
||||
* {
|
||||
* std::cerr << result.message() << '\n';
|
||||
* }
|
||||
* auto tick = reinterpret_cast<int (*)(int)>(ctx.findSymbol("plugin", "tick"));
|
||||
* @endcode
|
||||
*/
|
||||
class Context
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Creates an empty context.
|
||||
* @param options Callbacks to use; defaults to no callbacks at all (every event is
|
||||
* simply not observed, and every removed symbol keeps its previous build
|
||||
* running, exactly as if Options::onDeletedSymbol had returned null).
|
||||
*/
|
||||
explicit Context(Options options = {});
|
||||
|
||||
/// Unloads every module this context loaded (see this class's own doc comment).
|
||||
~Context();
|
||||
|
||||
Context(const Context&) = delete;
|
||||
Context& operator=(const Context&) = delete;
|
||||
|
||||
/// Leaves @p other in a valid but unspecified state, the same contract
|
||||
/// `std::unique_ptr` itself carries after a move: safe to destroy or to move-assign to
|
||||
/// (including moving something else into it), but calling load(), reload(), or
|
||||
/// findSymbol() on it afterwards is undefined behavior.
|
||||
Context(Context&& other) noexcept;
|
||||
|
||||
/// Unloads this context's own modules first, exactly as the destructor would, then
|
||||
/// takes over @p other's; @p other is left in the same moved-from state the move
|
||||
/// constructor leaves it in (see that constructor's own doc comment).
|
||||
Context& operator=(Context&& other) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Loads a module for the first time under a logical name.
|
||||
*
|
||||
* The file is not loaded in place: it is first copied to a uniquely named sibling and
|
||||
* that copy is loaded, which keeps a rebuild that overwrites @p path in place from
|
||||
* being mistaken for the already-loaded version. The copy is deleted again right after
|
||||
* loading on POSIX; on Windows it stays until the module is unloaded. This means the
|
||||
* directory containing @p path must be writable.
|
||||
*
|
||||
* The module's static constructors run as part of loading it.
|
||||
*
|
||||
* @param moduleName An arbitrary caller-chosen key used to look the module back up in
|
||||
* later reload()/findSymbol() calls; it does not need to match anything on disk.
|
||||
* @param path Path to the shared library (`.so`/`.dll`) to load. A relative path is
|
||||
* always resolved against the current working directory (never a system library
|
||||
* search path), exactly as a path you typed yourself would be.
|
||||
* @return Status::Ok, or Status::InvalidArgument if @p moduleName was already loaded
|
||||
* (call reload() instead of load() again for the same name), or
|
||||
* Status::LoadFailed (see the returned Result's message() for why).
|
||||
*/
|
||||
[[nodiscard]] Result load(std::string_view moduleName, const std::filesystem::path& path);
|
||||
|
||||
/**
|
||||
* @brief Loads a new build of a module, diffs its exported functions against the
|
||||
* previous version, and patches every one that is safe to patch in place.
|
||||
*
|
||||
* The previous version stays loaded (its entry points now jump into the new one), and
|
||||
* the new build's static constructors run. If the new build exports no functions at all
|
||||
* (including when its symbol table could not be read), every previously exported
|
||||
* function is reported through Options::onSymbolEvent as SymbolEvent::Removed.
|
||||
*
|
||||
* @param moduleName The same logical name passed to load().
|
||||
* @param newPath Path to the new build of the shared library; follows the same rules as
|
||||
* @p path in load().
|
||||
* @return Status::Ok, or a failing Status. On any failure, nothing was changed: the
|
||||
* previous version keeps running exactly as it was.
|
||||
*/
|
||||
[[nodiscard]] Result reload(std::string_view moduleName, const std::filesystem::path& newPath);
|
||||
|
||||
/**
|
||||
* @brief Resolves a symbol by name, against a module's newest loaded build.
|
||||
*
|
||||
* Always resolves against the freshest build loaded for @p moduleName, patched or not
|
||||
* -- useful for calling a function that did not exist yet when the host first started,
|
||||
* such as one reported through Options::onSymbolEvent as SymbolEvent::Added. Any
|
||||
* exported symbol resolves, data included; only functions are ever patched.
|
||||
*
|
||||
* @param moduleName The module to resolve @p symbolName in.
|
||||
* @param symbolName The symbol's real (possibly mangled) name.
|
||||
* @return The symbol's current address, or null if @p moduleName is unknown or
|
||||
* @p symbolName is not exported by its newest build.
|
||||
*/
|
||||
[[nodiscard]] void* findSymbol(std::string_view moduleName, std::string_view symbolName) const noexcept;
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
/**
|
||||
* @file Demangle.hpp
|
||||
* @brief hot_reload::demangle() -- a human-readable form of a real (possibly mangled) C++ name.
|
||||
*
|
||||
* Every C++ symbol name this library's events and lookups ever use is always the real
|
||||
* symbol-table name (whatever Context::findSymbol() needs to find it again) -- this function is
|
||||
* an opt-in convenience for *displaying* that name, never applied automatically, so there is
|
||||
* never a mismatch between what an event reports and what you would pass back in to look the
|
||||
* symbol up.
|
||||
*/
|
||||
namespace hot_reload
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Demangles @p name if it looks like a mangled C++ name; returns it unchanged
|
||||
* otherwise.
|
||||
*
|
||||
* Replaces hot_reload.h's `hot_reload_demangle()` / `hot_reload_free_string()` pair: the
|
||||
* result is returned by value, so there is nothing to separately free.
|
||||
*
|
||||
* @param name The real (possibly mangled) symbol name -- e.g. from
|
||||
* SymbolEventInfo::name.
|
||||
* @return The demangled form (GCC/Clang: Itanium ABI via `abi::__cxa_demangle`; MSVC:
|
||||
* `UnDecorateSymbolName`), or @p name itself, unchanged, if it does not demangle
|
||||
* (not a mangled C++ name at all, or memory ran out while demangling it) or this
|
||||
* build has no demangler for the compiler it was built with.
|
||||
*/
|
||||
[[nodiscard]] std::string demangle(std::string_view name);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include "hot_reload/SymbolEventInfo.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
|
||||
/**
|
||||
* @file Options.hpp
|
||||
* @brief hot_reload::Options -- callbacks a Context is constructed with.
|
||||
*/
|
||||
namespace hot_reload
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Callbacks and configuration a Context is constructed with.
|
||||
*
|
||||
* Replaces hot_reload.h's `HotReloadOptions`: `std::function` in place of a raw function
|
||||
* pointer plus a manually-threaded `void* user_data` -- a capturing lambda plays the role
|
||||
* `user_data` used to, so there is no separate field for it here at all.
|
||||
*
|
||||
* A pure aggregate with no behavior of its own, so -- unlike every other new public type in
|
||||
* this library -- there is no matching Options.cpp; see Context.hpp for the type whose
|
||||
* constructor actually reads these.
|
||||
*
|
||||
* @code
|
||||
* hot_reload::Options options;
|
||||
* options.onSymbolEvent = [](const hot_reload::SymbolEventInfo& event)
|
||||
* {
|
||||
* std::clog << event.summary() << '\n';
|
||||
* };
|
||||
* hot_reload::Context ctx(std::move(options));
|
||||
* @endcode
|
||||
*/
|
||||
struct Options
|
||||
{
|
||||
/**
|
||||
* @brief Called once per symbol on every Context::reload() call, synchronously, on the
|
||||
* calling thread, while the reload is in progress.
|
||||
*
|
||||
* @warning Do not call load(), reload(), or findSymbol() on the same Context from
|
||||
* inside this callback (see Context.hpp's thread-safety note: that Context's
|
||||
* own lock is already held, in a non-reentrant way, by the reload() call this
|
||||
* callback runs inside of -- doing so deadlocks rather than corrupts anything).
|
||||
*/
|
||||
std::function<void(const SymbolEventInfo& event)> onSymbolEvent;
|
||||
|
||||
/**
|
||||
* @brief Called instead of patching, once per SymbolEvent::Removed event, so the caller
|
||||
* can redirect old callers of a deleted function somewhere deliberate instead of
|
||||
* leaving its previous build running untouched (the default with no callback, or
|
||||
* with one that returns null).
|
||||
*
|
||||
* Called synchronously, on the calling thread, before the corresponding onSymbolEvent
|
||||
* call for the same symbol; the same warning about not calling load()/reload()/
|
||||
* findSymbol() applies.
|
||||
*
|
||||
* @param deletedSymbolName The removed function's real (possibly mangled) name.
|
||||
* @return The address of a replacement to redirect old callers to, or null to leave the
|
||||
* previous build's implementation running (the default).
|
||||
*/
|
||||
std::function<void*(std::string_view deletedSymbolName)> onDeletedSymbol;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include "hot_reload/Status.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* @file Result.hpp
|
||||
* @brief hot_reload::Result -- what Context::load() and Context::reload() return.
|
||||
*/
|
||||
namespace hot_reload
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief The outcome of one Context::load() or Context::reload() call: a Status, and the
|
||||
* platform's own message when that status is a failure worth explaining.
|
||||
*
|
||||
* This replaces the C API's split between a returned `HotReloadStatus` and a separate,
|
||||
* stateful `hot_reload_last_error()` call: every Result is self-contained, which is what
|
||||
* makes it safe to use from multiple threads without the two ever referring to different
|
||||
* calls -- see Context.hpp's own thread-safety note for why that split is not just a style
|
||||
* preference here.
|
||||
*
|
||||
* @code
|
||||
* hot_reload::Result result = ctx.load("plugin", "plugin.so");
|
||||
* if (!result) // or !result.ok()
|
||||
* {
|
||||
* std::cerr << hot_reload::toString(result.status()) << ": " << result.message() << '\n';
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
class Result
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a successful result.
|
||||
* @return A Result whose status() is Status::Ok and whose message() is empty.
|
||||
*/
|
||||
[[nodiscard]] static Result success() noexcept;
|
||||
|
||||
/**
|
||||
* @brief Constructs a failed result.
|
||||
* @param status The failure's status; must not be Status::Ok (use ok() for that).
|
||||
* @param message Why, in the platform's own words where there is one (see each
|
||||
* Status enumerator's own doc comment for what it carries); may be empty for a
|
||||
* failure that is fully explained by its status alone.
|
||||
* @return A Result whose status() is @p status and whose message() is @p message.
|
||||
*/
|
||||
[[nodiscard]] static Result failure(Status status, std::string message = {}) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Whether this result represents success.
|
||||
* @return True if status() is Status::Ok.
|
||||
*/
|
||||
[[nodiscard]] bool ok() const noexcept
|
||||
{
|
||||
return m_status == Status::Ok;
|
||||
}
|
||||
|
||||
/// Equivalent to ok() -- lets a Result be used directly as `if (result)`.
|
||||
[[nodiscard]] explicit operator bool() const noexcept
|
||||
{
|
||||
return ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This result's status.
|
||||
* @return The status passed to failure(), or Status::Ok for ok().
|
||||
*/
|
||||
[[nodiscard]] Status status() const noexcept
|
||||
{
|
||||
return m_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This result's message.
|
||||
* @return The message passed to failure(), or empty for ok() or a status that needed no
|
||||
* further explanation.
|
||||
*/
|
||||
[[nodiscard]] const std::string& message() const noexcept
|
||||
{
|
||||
return m_message;
|
||||
}
|
||||
|
||||
private:
|
||||
Result(Status status, std::string message) noexcept
|
||||
: m_status(status)
|
||||
, m_message(std::move(message))
|
||||
{
|
||||
}
|
||||
|
||||
Status m_status;
|
||||
std::string m_message;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
/**
|
||||
* @file Status.hpp
|
||||
* @brief hot_reload::Status -- the C++ API's outcome code, and toString() for logging it.
|
||||
*
|
||||
* Carries the same meaning as hot_reload.h's `HotReloadStatus`, which exists side by side with
|
||||
* this one: the plain C API is a wrapper around the C++ API this file is part of (see
|
||||
* `Context.hpp`), not the other way around, but both are supported, stable public surfaces --
|
||||
* `HotReloadStatus`'s own values are not going away. A Status is never constructed on its own;
|
||||
* see Result.hpp for the type that actually carries one back from a call.
|
||||
*/
|
||||
namespace hot_reload
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief The outcome of a Context::load() or Context::reload() call.
|
||||
*
|
||||
* @see Result, which pairs a Status with the human-readable message a failure deserves.
|
||||
*/
|
||||
enum class Status
|
||||
{
|
||||
/** The call did exactly what it says: loaded, or diffed-and-patched. */
|
||||
Ok,
|
||||
|
||||
/**
|
||||
* A required argument was empty or otherwise invalid, or Context::load() was called for
|
||||
* a module name that is already loaded -- call Context::reload() for that name instead.
|
||||
*/
|
||||
InvalidArgument,
|
||||
|
||||
/**
|
||||
* The platform could not load the image at all. Result::message() carries the
|
||||
* platform's own explanation -- this one status covers everything from a missing file,
|
||||
* to a genuinely corrupt image, to a shared library with an unresolved symbol
|
||||
* reference, and those need very different fixes. On Context::reload(), this is
|
||||
* non-destructive: the previously loaded version is left running untouched, exactly as
|
||||
* if reload had not been called.
|
||||
*/
|
||||
LoadFailed,
|
||||
|
||||
/** Context::reload() was called for a module name nothing has ever loaded. */
|
||||
NoPreviousVersion,
|
||||
|
||||
/**
|
||||
* This process's CPU architecture has no patch backend compiled in (see
|
||||
* `Architecture.hpp` -- currently x86, x86-64, ARM64, and ARM32). Nothing was patched;
|
||||
* Context::load() still succeeded, you just cannot Context::reload() on this target.
|
||||
*/
|
||||
UnsupportedArchitecture,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A short, human-readable label for @p status, suitable for logging.
|
||||
*
|
||||
* The C API's equivalent, `hot_reload_status_string()`, calls this.
|
||||
*
|
||||
* @param status The status to describe.
|
||||
* @return A `string_view` into a static string -- valid for the life of the program, safe to
|
||||
* copy out or hold onto indefinitely, never null.
|
||||
*/
|
||||
[[nodiscard]] std::string_view toString(Status status) noexcept;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
/**
|
||||
* @file SymbolEvent.hpp
|
||||
* @brief hot_reload::SymbolEvent -- what happened to one symbol during a Context::reload() call.
|
||||
*
|
||||
* Carries the same meaning as hot_reload.h's `HotReloadSymbolEvent`; see Status.hpp's file
|
||||
* comment for how the C and C++ APIs relate.
|
||||
*/
|
||||
namespace hot_reload
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief One symbol's outcome during a Context::reload() call, reported through
|
||||
* Options::onSymbolEvent.
|
||||
*
|
||||
* Reload diffs the previous build's exported functions against the new build's by name;
|
||||
* every name on either side gets exactly one of these.
|
||||
*/
|
||||
enum class SymbolEvent
|
||||
{
|
||||
/**
|
||||
* Present, and a function, in both builds, with a different compiled body -- and
|
||||
* successfully redirected: every existing caller of the old address, including one
|
||||
* that cached the pointer directly, now runs the new build's code.
|
||||
*/
|
||||
Patched,
|
||||
|
||||
/** A function in the new build with no same-named symbol in the previous one at all. */
|
||||
Added,
|
||||
|
||||
/**
|
||||
* In the previous build, not in the new one. The default: the old implementation keeps
|
||||
* running as-is unless Options::onDeletedSymbol supplies a stub for it.
|
||||
*/
|
||||
Removed,
|
||||
|
||||
/**
|
||||
* Present in both builds, but not classified as a function (ELF `STT_FUNC` in an
|
||||
* executable section, or a PE export whose address falls inside one) on at least one
|
||||
* side -- the mechanical half of this library's "only executable code is ever touched"
|
||||
* guarantee. Most often an exported variable; never patched, in either direction.
|
||||
*/
|
||||
SkippedNotCode,
|
||||
|
||||
/**
|
||||
* Present, and a function, in both builds -- but the previous build's compiled body
|
||||
* was not large enough to safely hold the jump this library writes over a function's
|
||||
* entry. Skipped rather than guessed. Compile the module with
|
||||
* `-fpatchable-function-entry=32,0` (GCC/Clang) or `/hotpatch` (MSVC) to stop this from
|
||||
* happening; `hot_reload_enable_patchable_functions()` in `cmake/HotReload.cmake`
|
||||
* applies whichever one your compiler needs.
|
||||
*/
|
||||
SkippedTooSmall,
|
||||
|
||||
/**
|
||||
* Present, and a function, and large enough -- but the operating system refused to make
|
||||
* that page of memory temporarily writable (an unusually locked-down environment).
|
||||
*/
|
||||
SkippedUnwritable,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A short, human-readable label for @p event, suitable for logging.
|
||||
* @param event The event to describe.
|
||||
* @return A `string_view` into a static string -- valid for the life of the program.
|
||||
*/
|
||||
[[nodiscard]] std::string_view toString(SymbolEvent event) noexcept;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include "hot_reload/SymbolEvent.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
/**
|
||||
* @file SymbolEventInfo.hpp
|
||||
* @brief hot_reload::SymbolEventInfo -- the argument Options::onSymbolEvent is called with.
|
||||
*/
|
||||
namespace hot_reload
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief One symbol's outcome during a Context::reload() call.
|
||||
*
|
||||
* Carries the same fields as hot_reload.h's `HotReloadSymbolEventInfo`; see Status.hpp's
|
||||
* file comment for how the C and C++ APIs relate.
|
||||
*
|
||||
* @note Valid only for the duration of the Options::onSymbolEvent call it was passed to --
|
||||
* in particular, @ref name points into storage Context owns, not this struct's own.
|
||||
* Copy out whatever you need (summary() is one convenient way to) before returning.
|
||||
*/
|
||||
struct SymbolEventInfo
|
||||
{
|
||||
/// The symbol's real (possibly mangled) name. See hot_reload::demangle() for a
|
||||
/// human-readable form.
|
||||
std::string_view name;
|
||||
|
||||
/// What happened to it. SymbolEvent::Removed is not a privileged "zero value" for this
|
||||
/// type the way, say, Status::Ok is for Status -- this default exists purely so a
|
||||
/// default-constructed SymbolEventInfo is well-defined rather than full of
|
||||
/// indeterminate values; every real one is fully populated by Context before use.
|
||||
SymbolEvent kind = SymbolEvent::Removed;
|
||||
|
||||
/// The symbol's address in the previous build, or null if not applicable (kind is
|
||||
/// SymbolEvent::Added).
|
||||
void* oldAddress = nullptr;
|
||||
|
||||
/// The symbol's address in the new build, or null if not applicable (kind is
|
||||
/// SymbolEvent::Removed).
|
||||
void* newAddress = nullptr;
|
||||
|
||||
/**
|
||||
* @brief A one-line, human-readable summary of this event -- e.g. for a log line.
|
||||
* @return Something like `"patched 'tick' (0x1234 -> 0x5678)"`; addresses are included
|
||||
* only where this event's kind() makes them meaningful.
|
||||
*/
|
||||
[[nodiscard]] std::string summary() const;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -182,9 +182,11 @@ extern "C"
|
||||
* @brief Opaque handle to a hot-reload context.
|
||||
*
|
||||
* See `cpp-style-guide.md` section 16 ("Do: use opaque pointers ... to
|
||||
* hide implementation details"). The real definition is the C++ class
|
||||
* `hot_reload::HotReloadContext` (`HotReloadContext.hpp`), never visible
|
||||
* from this header.
|
||||
* hide implementation details"). The real definition lives in
|
||||
* `c-api/HotReloadApi.cpp`: a small wrapper struct that owns a
|
||||
* `hot_reload::Context` (`Context.hpp`) by composition -- never a cast between unrelated
|
||||
* types, and never visible from this header. New C++ code should generally prefer
|
||||
* `hot_reload::Context` directly; see that header's own doc comment for why.
|
||||
*/
|
||||
typedef struct HotReloadContext HotReloadContext;
|
||||
|
||||
@@ -237,7 +239,7 @@ typedef enum HotReloadStatus
|
||||
|
||||
/**
|
||||
* This process's CPU architecture has no patch backend compiled in
|
||||
* (see `Architecture.hpp` -- currently x86, x86-64, and ARM64).
|
||||
* (see `Architecture.hpp` -- currently x86, x86-64, ARM64, and ARM32).
|
||||
* Nothing was patched; hot_reload_load() still succeeded, you just
|
||||
* cannot hot_reload_reload() on this target.
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user