API — ICoreBlocks/ICoreBlockLibrary
The public contract of 5 header(s) under src/ICoreBlocks/ICoreBlockLibrary — 7 class/struct definition(s), 32 declaration(s). Each section shows the header's banner and its public (and protected-virtual) surface exactly as the file writes it.
ICoreCRuntime.h#
src/ICoreBlocks/ICoreBlockLibrary/Runtimes/CRuntime/ICoreCRuntime.h
ICoreCBlockProgram#
ICoreCRuntime.h:24 · class · pImpl · 3 declaration(s)
One compiled user program: the user's C source, prefixed with the runtime's prelude (the ICoreCMatrix struct + helpers), compiled by the system C compiler into a shared library in a private temp di...
class ICoreCBlockProgram {
public:
ICoreCBlockProgram();
~ICoreCBlockProgram();
// Reload the shared library so the user code's statics are re-zeroed
// (called at every simulation start). False + errorOut when the reload
// fails; the program is unusable afterwards and must be recompiled.
bool resetState(std::string& errorOut);
// One solver step: compute(t, u, uCount, y, yCount). Every u entry is
// handed to the user as a row-major ICoreCMatrix; y arrives as
// `outputCount` matrices whose rows/cols the USER must set (they start
// at 0x0), each with a data capacity of ICORE_MAX_MATRIX_ELEMENTS
// doubles. Unlike Python — where the returned list's length carries the
// arity — C outputs are caller-allocated, so the expected count is a
// parameter. Returns false with a compiler/loader/contract error message
// without touching yOut.
//
// Every port is ICoreDouble, which is what this runtime assumed before
// signal types existed. Forwards to the typed form below with empty type
// lists, so a double-only block's crossing is unchanged byte for byte.
bool run(double t, const std::vector<ICoreMatrix>& u, size_t outputCount,
std::vector<ICoreMatrix>& yOut, std::string& errorOut);
// The typed form. `uTypeIds` / `yTypeIds` are signal-type registry ids
// ("ICoreDouble", "ICoreInt32", "ICoreString", …), one per port; an EMPTY
// list means all-double, and so does an id the registry does not know.
// They reach the user's code as each matrix's read-only `kind` tag, which
// is why they are worth carrying at all: a C block cannot ask a port
// anything, so without the tag it cannot tell an int32 input from a double
// one and must guess.
//
// `uText` carries a String input's value (indexed like `u`; entries for
// numeric ports are ignored) and `yTextOut` receives a String output's.
// They are separate from the matrices because a string cannot ride in one:
// a String port's matrix is a 1x1 zero placeholder and the value lives
// beside it, which is exactly the shape the port's own
// solver environment already stores it in.
//
// yTextOut is sized to outputCount on success; a numeric output's entry is
// empty. A String output does NOT have to set rows/cols — its matrix is a
// placeholder, so 1x1 is pre-set for it and the "compute left rows/cols at
// 0x0" error cannot fire on one.
bool run(double t,
const std::vector<ICoreMatrix>& u,
const std::vector<std::string>& uTypeIds,
const std::vector<std::string>& uText,
size_t outputCount,
const std::vector<std::string>& yTypeIds,
std::vector<ICoreMatrix>& yOut,
std::vector<std::string>& yTextOut,
std::string& errorOut);
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreCRuntime#
ICoreCRuntime.h:85 · class · 2 declaration(s)
class ICoreCRuntime {
public:
// False when no C compiler (cc / clang / gcc) is on the PATH.
static bool isAvailable();
// First line of `<compiler> --version` plus its path — for diagnostics.
static std::string runtimeInfo();
// Element capacity of every output matrix handed to compute — the
// prelude's ICORE_MAX_MATRIX_ELEMENTS.
static constexpr size_t MAX_MATRIX_ELEMENTS = 4096;
// Writes prelude + user source to a temp dir, compiles it into a shared
// library and validates it exports compute (the prelude's prototype makes
// a wrong signature a compile error, not a mid-run surprise). Null +
// errorOut (the compiler's stderr included) on any failure. Thread-safe;
// may be called from the solver worker thread.
static std::unique_ptr<ICoreCBlockProgram> compileBlockProgram(
const std::string& sourceCode, std::string& errorOut);
};
};
ICorePythonRuntime.h#
src/ICoreBlocks/ICoreBlockLibrary/Runtimes/PythonRuntime/ICorePythonRuntime.h
ICorePythonBlockProgram#
ICorePythonRuntime.h:10 · class · pImpl · 3 declaration(s)
One compiled user program (a module scope that must define compute(t, u, state)).
class ICorePythonBlockProgram {
public:
ICorePythonBlockProgram();
~ICorePythonBlockProgram();
// Fresh empty `state` dict (called at every simulation start).
void resetState();
// One solver step: y = compute(t, u, state). Every u entry is handed to
// the user as a 2D numpy float matrix; the returned sequence is coerced
// back to 2D ICoreMatrix values (scalars/1D rows are promoted via
// numpy.atleast_2d). Returns false with a Python-style error message —
// wrong return arity/shape included — without touching yOut.
//
// Every port is ICoreDouble, which is what this runtime assumed before
// signal types existed. Forwards to the typed form below with empty type
// lists, so a double-only block's crossing is unchanged.
//
// ⚠ NOTHING IN THIS TREE CALLS IT ANY MORE, deliberately: `Python_Code`'s
// SIZING call used to, and sizing a block through a boundary that is not
// the one it will run through is how a Bool port arrived as an array at
// sizing and as a `bool` at t = 0. It is kept because
// ICoreCBlockProgram carries the same pair and an untyped caller is a
// reasonable thing for an SDK to offer — but if you are reaching for it
// from inside this tree, you almost certainly want the typed form.
bool run(double t, const std::vector<ICoreMatrix>& u,
std::vector<ICoreMatrix>& yOut, std::string& errorOut);
// The typed form, and the counterpart of ICoreCBlockProgram's. `uTypeIds`
// / `yTypeIds` are signal-type registry ids, one per port; an EMPTY list
// means all-double, and so does an id the registry does not know.
//
// WHAT A TYPE LOOKS LIKE FROM PYTHON. C gets a `kind` tag because a C
// struct has nowhere else to put one; numpy already has the answer, so an
// input port arrives as an array of ITS OWN DTYPE — bool_ for a Boolean,
// int32 for an ICoreInt32, float32 for an ICoreSingle. `u[i].dtype` is
// therefore how a Python block asks what a port is, and it needs no
// vocabulary of ours to do it.
//
// …EXCEPT AT 1x1, where a Boolean or Integer port hands over a native
// Python `bool` / `int` instead, so that `if u[0]:` and `u[0] % 2` mean
// what a Python author expects rather than what a 1x1 array does. A
// FLOATING port keeps its array at every size, ICoreDouble included: every
// block written against this runtime indexes u[i] as a matrix, and an
// ICoreSingle needs the array because a Python float is a double and the
// dtype is the only place float32 survives.
//
// THE RETURNED LIST IS ADMITTED PER OUTPUT PORT, NOT QUANTIZED. A value
// the port's type cannot carry — text for a numeric port, an array for a
// String one — is refused with a message naming the output's position in
// the caller's own return list, the type the port was declared with, and
// what came back. Applying the KIND is not this boundary's job and must not
// become it: that happens exactly once, at the port write
// (ICorePortSolverEnvironment::quantizeSignalToType).
//
// `uText` carries a String input's value and `yTextOut` receives a String
// output's, for the same reason as in the C runtime: a string cannot ride
// in the matrix, so it travels beside it (a String port's matrix is a 1x1
// zero placeholder). A String output may be returned as a `str` or as
// anything `str()` accepts; yTextOut is sized to the returned arity on
// success and a numeric output's entry is empty.
bool run(double t,
const std::vector<ICoreMatrix>& u,
const std::vector<std::string>& uTypeIds,
const std::vector<std::string>& uText,
const std::vector<std::string>& yTypeIds,
std::vector<ICoreMatrix>& yOut,
std::vector<std::string>& yTextOut,
std::string& errorOut);
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICorePythonRuntime#
ICorePythonRuntime.h:87 · class · 2 declaration(s)
class ICorePythonRuntime {
public:
// False when the app was built without ICORE_PYTHON_EMBED (no headers /
// libpython at build time) or the interpreter failed to boot.
static bool isAvailable();
// "Python 3.14.4 | numpy 2.3.5" — for diagnostics.
static std::string runtimeInfo();
// Compiles user source in a fresh module scope (numpy pre-imported as
// both `np` and `numpy`) and validates it defines a callable
// compute(t, u, state) taking exactly 3 parameters. Null + errorOut on
// any failure. Thread-safe; may be called from the solver worker thread.
static std::unique_ptr<ICorePythonBlockProgram> compileBlockProgram(
const std::string& sourceCode, std::string& errorOut);
};
};
ICoreUserBlockDefinition.h#
src/ICoreBlocks/ICoreBlockLibrary/UserBlocks/ICoreUserBlockDefinition.h
ICoreUserBlockDefinition#
ICoreUserBlockDefinition.h:33 · class · 9 declaration(s)
One parsed .iblock file — a USER-DEFINED block definition, the unit the Block Wizard writes and the user block library (ICoreUserBlockLibrary) scans.
class ICoreUserBlockDefinition {
public:
enum class Kind {
Composite, // body = recipe text (ICoreRecipeSerializer output)
Python // body = Python source with a compute(t, u, state)
};
// ---- identity ---------------------------------------------------------
ICoreString uuid; // v4; identity across renames — never regenerate on edit
ICoreString name; // leaf segment, identifier-safe [A-Za-z0-9_]
ICoreString family; // middle segment, identifier-safe
ICoreString summary; // one line, shown by the navigator entry
ICoreString created; // "YYYY-MM-DD"; informational, not validated
ICoreString modified;
Kind kind = Kind::Composite;
// ---- ports ------------------------------------------------------------
// Counts are what the navigator preview draws (the analogue of
// registerInitialPorts). Labels are empty or exactly count-sized; a side
// with 2+ ports MUST carry labels — same rule as ADDING_NEW_BLOCKS.md §3,
// and validate() enforces it. Labels may not contain commas (they are
// comma-separated in the header).
int inputCount = 0;
int outputCount = 0;
ICoreStringList inputLabels;
ICoreStringList outputLabels;
// Signal-type ids, one per port, or EMPTY meaning "all ICoreDouble".
//
// Empty is what every .iblock written before this key existed carries, and
// it is not a special case to remember: it is the same thing the counts
// already do for labels, and it is why an old file loads unchanged. A
// present list must be exactly count-sized and every entry must be a
// registry id -- validate() enforces both, so a definition that parsed is
// one the model will accept.
ICoreStringList inputTypes;
ICoreStringList outputTypes;
// The type of one port, resolved: the declared id, or ICoreDouble when the
// list is absent or short. The three callers that build ports from a
// definition ask through this rather than indexing, so "absent means
// double" is stated once.
[[nodiscard]] ICoreString inputTypeAt(int index) const;
[[nodiscard]] ICoreString outputTypeAt(int index) const;
// ---- payloads ---------------------------------------------------------
ICoreString iconSvg; // may be empty: navigator falls back to a monogram
ICoreString descriptionHtml; // may be empty: hover card shows the summary
ICoreString body; // must be non-empty for Kind::Python
// ---- file text <-> definition -----------------------------------------
// False leaves `err` explaining which line or section refused, in words a
// user can act on ("line 4: ...", "missing section ...ICON...").
// `out` is reset first and is only trustworthy when parse returns true.
static bool parse(const ICoreString& fileText, ICoreUserBlockDefinition& out,
ICoreString& err);
// The exact text parse() accepts. Assumes validate() passed — serialize
// does not re-check.
ICoreString serialize() const;
// The rules a definition must satisfy before it is written or listed:
// uuid present, identifier-safe name/family, non-negative counts, label
// count/mandatory-label rules, non-empty Python body. parse() runs this
// last, so a parsed definition is always a valid one.
bool validate(ICoreString& err) const;
// ---- naming -----------------------------------------------------------
// "My_Blocks/<family>/<name>" — the navigator address. A pseudo-type: it
// is never registered anywhere, and the grand family is reserved for us
// (USER_BLOCK_WIZARD.md §8).
ICoreString pseudoType() const;
static const char* grandFamily();
// ---- helpers ----------------------------------------------------------
static bool isIdentifierSafe(const ICoreString& s); // non-empty, [A-Za-z0-9_]
static ICoreString generateUuid(); // random v4, lowercase
static ICoreString kindName(Kind kind); // "composite" / "python"
static bool kindFromName(const ICoreString& text, Kind& out);
static constexpr int formatMajorVersion = 1;
bool operator==(const ICoreUserBlockDefinition& other) const;
bool operator!=(const ICoreUserBlockDefinition& other) const;
};
};
ICoreUserBlockLibrary.h#
src/ICoreBlocks/ICoreBlockLibrary/UserBlocks/ICoreUserBlockLibrary.h
ICoreUserBlockLibrary#
ICoreUserBlockLibrary.h:37 · class · nested SkippedFile · 13 declaration(s)
The user's per-app library of .iblock definitions: one folder tree under the application home, scanned off disk, never cached across calls.
class ICoreUserBlockLibrary {
public:
// One unreadable file from the last scan, and why. The navigator's manage
// dialog lists these — a silently absent block reads as "the app lost my
// work", which is worse than any error message.
struct SkippedFile {
ICoreString path;
ICoreString reason;
};
// Every definition on disk right now, family folders in name order, files
// in name order inside each. Rescans on every call (the set is small, and
// a cache would go stale the moment a user drops a file in with the app
// running — the same reasoning as ICoreTemplateLibrary::all()).
static ICoreList<ICoreUserBlockDefinition> all();
// What the most recent scan could not read. Refreshed by all() and by the
// mutating calls below (each rescans to validate its inputs).
static ICoreList<SkippedFile> skippedInLastScan();
// Lookups over a fresh scan. False leaves `out` untouched.
static bool findByUuid(const ICoreString& uuid, ICoreUserBlockDefinition& out);
static bool findByPseudoType(const ICoreString& pseudoType, ICoreUserBlockDefinition& out);
// Family folder names (sorted, may include empty families) — the wizard's
// family combo lists these.
static ICoreStringList families();
// Write `def` into the library at pathFor(def). Collisions:
// * target file exists with the SAME uuid -> update in place;
// * target file exists with a DIFFERENT uuid -> refused unless
// `overwrite` (the wizard asks the user first);
// * same uuid already lives at a DIFFERENT path -> treated as a
// rename/move: the old file is removed after the new one is written.
// False leaves `err` filled and the library unchanged.
static bool save(const ICoreUserBlockDefinition& def, bool overwrite, ICoreString& err);
// Bring an external .iblock into the library: parse + validate `source`,
// then save() it where its own header says it belongs. `imported` receives
// the definition on success (the caller wants the name for its "Imported
// <block>" message). The source file is never modified.
static bool importFile(const std::filesystem::path& source, bool overwrite,
ICoreUserBlockDefinition& imported, ICoreString& err);
// Copy the block's library file to `destination`, byte for byte. Refuses
// to overwrite an existing destination — the caller's file dialog owns
// that conversation.
static bool exportTo(const ICoreString& uuid, const std::filesystem::path& destination,
ICoreString& err);
// Delete the block's file; prunes its family folder if that leaves it
// empty. False when no such uuid or the delete failed.
static bool remove(const ICoreString& uuid, ICoreString& err);
// "<application home>/Blocks". Created on first look, like the user
// templates folder, so there is always somewhere to drop a file.
static std::filesystem::path userBlocksFolderPath();
// TEST SEAM. The regression sandbox repoints the DOCUMENTS folder but the
// application home is deliberately assigned once and never moves — so a
// sandboxed case points this library at a scratch tree instead. An empty
// path restores the real app home. Never called outside the test suite.
static void setRootOverrideForTests(const std::filesystem::path& root);
// "<root>/<family>/<name>.iblock" for this definition.
static std::filesystem::path pathFor(const ICoreUserBlockDefinition& def);
// Change notification, toolkit-free: save/import/remove fire every
// subscriber after the library has changed on disk. The navigator's
// rebuild hangs off this. Subscribers run on the caller's thread.
static int subscribeToChanges(std::function<void()> onChanged);
static void unsubscribeFromChanges(int token);
// The drag-and-drop format a user-block navigator entry carries and the
// canvas accepts; the payload is a definition's uuid. Declared here next
// to the uuids it quotes, for the same no-drift reason as
// ICoreTemplateLibrary::dragMimeType().
static const char* dragMimeType();
};
};
ICoreUserBlockMaterializer.h#
src/ICoreBlocks/ICoreBlockLibrary/UserBlocks/ICoreUserBlockMaterializer.h
ICoreUserBlockMaterializer#
ICoreUserBlockMaterializer.h:33 · class · 0 declaration(s)
Stamps a user block definition into a live diagram (USER_BLOCK_WIZARD.md §7).
class ICoreUserBlockMaterializer {
public:
// Stamp `def` into `parent` at `position` (origin-anchor coordinates,
// +y up, exactly like move()). `createdName` receives the name the
// instance actually got — a sibling clash gets a numbered name instead.
static ICoreRecipeFileTransfer::Result stamp(ICoreSubsystemTreeNode* parent,
const ICoreUserBlockDefinition& def,
const ICorePoint& position,
ICoreString* createdName = nullptr);
// The drop handler's form: resolve `uuid` in ICoreUserBlockLibrary, then
// stamp. Fails with a user-readable reason when the uuid is not in the
// library (e.g. the file was deleted while the navigator still showed it).
static ICoreRecipeFileTransfer::Result stampByUuid(ICoreSubsystemTreeNode* parent,
const ICoreString& uuid,
const ICorePoint& position,
ICoreString* createdName = nullptr);
};
};