Generated reference › API — ICoreBlocks/ICoreModel
kind: generated#api#icoreblocks-icoremodel

API — ICoreBlocks/ICoreModel

The public contract of 32 header(s) under src/ICoreBlocks/ICoreModel — 37 class/struct definition(s), 774 declaration(s). Each section shows the header's banner and its public (and protected-virtual) surface exactly as the file writes it.

HeaderDefinesDeclarationsBases
ICoreBlockFactory.hICoreBlockFactory20—
ICoreSubsystemTreeNode.hICoreSubsystemTreeNotifier, ICoreSubsystemTreeNode77—
ICoreSubsystemTreeNodeRegistry.hICoreSubsystemTreeNodeRegistry21—
ICoreBlock.hICoreBlock93—
ICoreFaceDrawing.hICoreFaceDrawingColor, ICoreFaceDrawingGradientStop, ICoreFaceDrawingPaint, ICoreFaceDrawingTransform, ICoreFaceDrawingPathStep, ICoreFaceDrawing7—
ICoreBlockConfigVariable.h—0—
ICoreBlockConfigurator.hICoreBlockConfigurator31—
ICoreBlockReferences.hICoreBlockReferences5—
ICoreActionDriver.hICoreActionDriver4—
ICoreBlockSubsystemGate.hICoreBlockSubsystemGate11—
ICoreMergeRule.hICoreMergeRule6—
ICoreSubsystemActivity.hICoreSubsystemActivity24—
ICoreSubsystemControls.hICoreSubsystemControls22—
ICoreBusSpec.hICoreBusSpec12—
ICoreMessageDelivery.hICoreMessageDelivery9—
ICoreMessageTraffic.hICoreMessageTraffic9—
ICorePort.hICorePort48—
ICoreSignalType.hICoreSignalType7—
ICoreSignalValue.hICoreSignalValue27—
ICoreDataStoreRegistry.hICoreDataStoreRegistry13—
ICoreEnumRegistry.hICoreEnumRegistry5—
ICoreEditorHost.hICoreEditorHost23—
ICoreLink.hICoreLink39—
ICoreLinkBranch.hICoreLinkBranch82—
ICoreBlockSolverEnvironment.hICoreBlockSolverEnvironment94—
ICorePortSolverEnvironment.hICorePortSolverEnvironment16—
ICoreTreeNodeSolverEnvironment.hICoreTreeNodeSolverEnvironment8—
ICoreUnits.hICoreUnits12—
ICoreVariablesSpace.hICoreVariablesSpace31—
ICoreVariablesSpaceVariable.hICoreVariablesSpaceVariable8—
ICoreWorkspaceObjects.hICoreWorkspaceObjects1—
ICoreVariantControls.hICoreVariantControls9—

ICoreBlockFactory.h#

src/ICoreBlocks/ICoreModel/ICoreBlockFactory.h

ICoreBlockFactory#

ICoreBlockFactory.h:7 · class · nested InitialPorts, InitialPortTypes · 20 declaration(s)

class ICoreBlockFactory {
public:

    // ====================[ Solver Environments ]======================
    using SolverEnvCreator = std::function<std::unique_ptr<ICoreBlockSolverEnvironment>(ICoreBlock*)>;
    static void registerBlockSolvEnv(const std::string& type, SolverEnvCreator creator);
    static std::unique_ptr<ICoreBlockSolverEnvironment> createBlockSolverEnvironment(const std::string& type, ICoreBlock* block);
    static std::unordered_map<std::string, SolverEnvCreator>& getSolverEnvRegistry();

    // ====================[ Manager ]======================
    static void setBlockUp(ICoreBlock* blockToSetUp);
    static bool isBlockTypeValidSubsystem(const std::string &blockType);

    // ====================[ Legacy spellings ]======================
    // The Control_Systems "Continues" family was renamed to "Continuous" on
    // 2026-09-09 (it was a typo). That word is persisted in saved projects in TWO
    // places — the block type path ("Control_Systems/Continues/State_Space") and
    // the state-space config keys ("A - Continues") — so a project saved before
    // the rename carries the old spelling and must still open. Every lookup keyed
    // by one of those strings falls back through here on a miss; the caller then
    // stores the canonical form, so re-saving writes the new spelling and the
    // project heals itself.
    //
    // Rewrites the FIRST occurrence only, which is the whole of both shapes.
    // Returns `name` unchanged when there is nothing to migrate — so a hit on
    // this path is always a genuinely old file, never a cost on the common case.
    // Delete this, its call sites and its self-test once projects predating the
    // rename need no longer open.
    static std::string canonicalizeLegacyName(const std::string& name);

    // ====================[ Block Icons ]======================
    static void registerIconSVG(const std::string &type, const char *svg);

    // Falls back to the legacy spelling on a miss (canonicalizeLegacyName), so a
    // caller that only needs "is this a real type / draw it" keeps working for a
    // project saved before the rename.
    static const char *getBlockIconSVG(const std::string &type);

    // No legacy fallback: answers "is a type registered under EXACTLY this
    // spelling". A caller that has to tell an old spelling apart from a current
    // one -- resolveBlockType, which returns the canonical type it resolved to --
    // needs the distinction that getBlockIconSVG deliberately hides.
    static const char *getBlockIconSVG_exact(const std::string &type);

    // All registered block type strings (the keys of the icon catalog). The icon
    // registry is populated by every block TU's static initializer, so this is the
    // authoritative list of valid types. Used to validate a requested type and to
    // resolve a leaf name (e.g. "Gain") to its full hierarchical type path.
    static std::vector<std::string> getAllRegisteredTypes();

    // ====================[ Block Descriptions ]======================
    // The block's user-facing description, as HTML (see ADDING_NEW_BLOCKS.md).
    // Registered by each block TU next to its icon, and handed to the block's
    // configurator by its solver-environment constructor — which needs a live
    // ICoreBlock. The library navigator has none: it lists TYPES, so it reads the
    // description from here, exactly as it reads the icon and the port counts.
    static void registerDescriptionHTML(const std::string& type, const std::string& html);

    // Empty for an unregistered type, so a caller shows nothing rather than a
    // placeholder. Returns a reference into the registry — valid for the process's
    // lifetime, since nothing ever erases from it.
    static const std::string& getBlockDescriptionHTML(const std::string& type);

    // ====================[ Initial Ports ]======================
    // How many ports a freshly created block of a type comes up with. Registered by
    // each block TU next to its icon, and it must mirror the createNewPort() calls in
    // that block's solver-environment constructor — which is where the real ports are
    // born, and which needs a live ICoreBlock on a canvas inside a subsystem tree.
    // A library preview has none of that, so it reads the counts from here instead.
    struct InitialPorts {
        int inputCount = 0;
        int outputCount = 0;
    };
    static void registerInitialPorts(const std::string& type, int inputCount, int outputCount);

    // Zeroed for an unregistered type, so a caller draws nothing rather than guessing.
    static InitialPorts getInitialPorts(const std::string& type);

    // ====================[ Initial Port Types ]======================
    // The signal type of each of those initial ports, in the order the block's
    // solver-environment constructor creates them: inputs in createNewPort()
    // order, then outputs in theirs. Registered beside the counts, by the same
    // TU, and under the SAME RULE — it must mirror the constructor, because
    // nothing checks it at run time. A preview is drawn for a type of which no
    // block exists, so there is no port to ask.
    //
    // A block that registers no types is all-ICoreDouble, which is every block
    // in the tree today. getInitialPortTypes() answers that for it — padded to
    // the registered counts with ICoreSignalType::DEFAULT_ID — so a caller
    // never has to decide for itself what "absent" means, and never has to
    // index-check the vector against the count.
    struct InitialPortTypes {
        std::vector<std::string> inputTypeIds;
        std::vector<std::string> outputTypeIds;
    };
    static void registerInitialPortTypes(const std::string& type,
                                         const std::vector<std::string>& inputTypeIds,
                                         const std::vector<std::string>& outputTypeIds);

    // Always exactly getInitialPorts(type).inputCount / .outputCount long, every
    // entry a known registry id. An unregistered type answers with two empty
    // lists, matching the zeroed counts it gets from getInitialPorts().
    static InitialPortTypes getInitialPortTypes(const std::string& type);

    // A block whose port TYPES are settled at config load or at build, not at
    // construction (FEATURES_TO_ADD.md BF1.11): Data Type Conversion's output
    // follows its config, a Bus Selector's follows the element it picks. Until
    // then such a port carries its declared type, and checking a connection
    // against that would refuse one the build is about to make right -- a
    // replayed recipe connects before any config load. So a connection with a
    // late end is not type-checked when it is made; the build checks it, after
    // the types are settled (ICorePort::acceptsConnectionFrom).
    static void registerPortTypesSettleLater(const std::string& type, bool inputs, bool outputs);
    static bool portTypesSettleLater(const std::string& type, bool outputSide);

    // Where the library navigator lists a `Private/` type, as a public
    // "Grand/Family/Child" path; the type itself keeps its name, so saved models,
    // recipes and the bridge are untouched. A Private type with no placement is
    // not listed. `typeAtLibraryPlacement` answers the type a listed path makes,
    // or the path itself when nothing is placed there.
    static void registerLibraryPlacement(const std::string& type, const std::string& placement);
    static std::string libraryPlacementOf(const std::string& type);
    static std::string typeAtLibraryPlacement(const std::string& placement);

};
};

ICoreSubsystemTreeNode.h#

src/ICoreBlocks/ICoreModel/ICoreSubsystemTreeNode.h

ICoreSubsystemTreeNotifier#

ICoreSubsystemTreeNode.h:41 · class · final · 0 declaration(s)

⚠⚠ THE MODEL'S "A NAME CHANGED" SIGNAL, AND IT HAD TO LIVE DOWN HERE (W10.89, 2026-09-20).

class ICoreSubsystemTreeNotifier final {
public:
    ICoreSignal<> onSubsystemTreeNameChanged;
};
};

ICoreSubsystemTreeNode#

ICoreSubsystemTreeNode.h:48 · class · pImpl · 77 declaration(s)

class ICoreSubsystemTreeNode {
public:
    // How getTreeWidgetItems() orders the entries it builds for one level of the
    // navigator. The enum lives here rather than in the view because the ordering
    // is applied while the items are built, and the builder is this class -- the
    // view is L8 and this is L3, so the mode travels DOWN as an argument.
    //
    //   Name          -- every entry, subsystems and blocks alike, by name (A-Z).
    //   Kind          -- subsystems first, then blocks; by name within each group.
    //   LastModified  -- most recently modified first (see markModified()).
    //   DateCreated   -- most recently created first.
    //
    // Blocks carry no timestamps of their own, so under the two date modes they
    // sort after every subsystem, among themselves by name. Ties in every mode
    // break on name, so the order is total and a rebuild never reshuffles.
    enum class SortMode { Name, Kind, LastModified, DateCreated };

    explicit ICoreSubsystemTreeNode(ICoreSubsystemTreeNode* parent = nullptr, const std::string& initName = "~auto");

    ICoreBlock* createNewBlock(const std::string &type);
    ICoreLink* createNewLink(std::string type);
    ICoreCanvasArea* createNewCanvasArea();
    ICoreCanvasTextBox* createNewTextbox();
    ICoreImage* createNewImage();

    void deleteBlock(ICoreBlock* block);
    void deleteLink(ICoreLink* link);
    void deleteCanvasArea(ICoreCanvasArea* area);
    void deleteTextbox(ICoreCanvasTextBox* textbox);
    void deleteImage(ICoreImage* image);

    void giveOwnershipUp_Block(ICoreBlock* block);
    void giveOwnershipUp_Link(ICoreLink* link);
    void giveOwnershipUp_CanvasArea(ICoreCanvasArea* area);
    void giveOwnershipUp_Textbox(ICoreCanvasTextBox* textbox);
    void giveOwnershipUp_Image(ICoreImage* image);

    void acquireOwnership_Block(ICoreBlock* block);
    void acquireOwnership_Link(ICoreLink* link);
    void acquireOwnership_CanvasArea(ICoreCanvasArea* area);
    void acquireOwnership_Textbox(ICoreCanvasTextBox* textbox);
    void acquireOwnership_Image(ICoreImage* image);
    void moveLinksFromAllDescendentsToTrash() const;

    void deleteLinksFromAllDescendentsPermanently() const;

    bool isBlockNameAvailable(const std::string& newName, ICoreBlock* blockToExclude);
    bool isLinkNameAvailable(const std::string& newName, const ICoreLink* linkToExclude) const;
    bool isCanvasAreaNameAvailable(const std::string& newName, ICoreCanvasArea* areaToExclude) const;
    bool isTextBoxNameAvailable(const std::string& newName, ICoreCanvasTextBox* noteToExclude);
    bool isImageNameAvailable(const std::string& newName, const ICoreImage* imageToExclude) const;

    void runLinkPathOptimizerToAllBlocks() const;

    void deleteChildTreeNode(ICoreSubsystemTreeNode* nodeToDelete);

    void permanentlyDeleteAllChildren();
    // The same, except the gate blocks -- the subsystem's own ports -- stay, and
    // so do the parent's links to those ports. What replacing one subsystem's
    // contents needs (the agent-bridge board, AB.16): the level above is not
    // rewired.
    void permanentlyDeleteAllChildrenExceptGates();

    void reArrangeAllChildrenZOrder() const;

    ICoreRect calculateReqAreaOnCanvas() const;

    ICoreSubsystemTreeNode* findTreeNodeByPath(const std::string& fullPath);

    // This node as ONE navigator row, as the cells of that row left to right:
    //   [0] the name, with the subsystem icon and the entry-type / entry-path
    //       roles the view reads back -- the cell that owns the children;
    //   [1] last modified, formatted for display;
    //   [2] date created, likewise.
    //
    // Always three cells, even for a caller showing one column: a tree whose
    // rows disagree about their cell count is a Qt model bug waiting to happen,
    // and hiding a column is the view's job (ICoreTreeView::setColumnSizing),
    // not the model's. Blocks appear as rows here too and have no timestamps of
    // their own, so their detail cells are empty rather than invented.
    //
    // Ownership transfers to whatever the cells are appended to -- see
    // ICoreStandardItem's Group B note.
    [[nodiscard]] std::vector<ICoreStandardItem*> getTreeWidgetItems(SortMode sortMode = SortMode::Name) const;

    // How the two detail cells above are rendered, and the ONE place that
    // decision lives -- the navigator's column titles are written against it.
    // Local time, seconds dropped: a diagram edit is not a stopwatch reading,
    // and the narrow columns these sit in have no room for them.
    [[nodiscard]] static std::string formatTimestampForDisplay(long long msSinceEpoch);

    void pushBackChildTreeNode(ICoreSubsystemTreeNode *newChild);

    void eraseChildTreeNode(const ICoreSubsystemTreeNode *childToErase);

    void checkOutCanvasPointersBeforeDeletion(ICoreCanvas* canvasToCheckOut);

    // ======================= Setters ==============================

    void setParent(ICoreSubsystemTreeNode* parent);
    void setName(const std::string& newName) const;
    void reconstructPath();
    void setLoadedToCanvas(ICoreCanvas* canvas);

    // ---------------- The two timestamps every subsystem carries
    //
    // Milliseconds since the Unix epoch, in the same units ICoreDateTime takes
    // (fromMSecsSinceEpoch), so a caller that wants to SHOW one hands it straight
    // over. A plain integer rather than a formatted string on purpose: it sorts,
    // it round-trips through the recipe without a parser, and it carries no
    // locale.
    //
    // Both are stamped with "now" by the constructor and by resetToInitialState,
    // so a node always has a real pair -- there is no invalid/zero state to guard
    // at the call sites. The setters exist for ONE caller each: the recipe
    // interpreter, restoring what ICoreRecipeSerializer wrote (`h.setTimes(...)`
    // / `subsystemTimes(...)`). Everything else uses markModified().
    void setCreatedTimeMs(long long msSinceEpoch);
    void setLastModifiedTimeMs(long long msSinceEpoch);

    // Stamps THIS node's last-modified with now, and every ancestor up to the
    // root with it -- a change inside a subsystem is a change to each diagram
    // that contains it.
    //
    // ⚠ What it does NOT cover: this is called from the structural edits that go
    // through a tree node (an object created, deleted, renamed, or moved in or
    // out of the level). Editing a block's own config or dragging it a few pixels
    // never reaches this class and so does not stamp anything. That is the
    // documented meaning of the property -- "when the contents of this subsystem
    // last changed shape" -- not an oversight.
    void markModified();

    // ---------------- Identity (per-subsystem undo, H2)
    //
    // A session-unique number, never 0, that names ONE subsystem for as long as
    // it exists. Per-subsystem undo histories are keyed by it, because the two
    // other handles on a node both lie:
    //   * its POINTER -- nodes come from a recycling pool, so after a delete the
    //     same address is handed to an unrelated subsystem;
    //   * its PATH -- a rename rewrites the path of the node and every descendant.
    // A rename keeps the id; a node taken back out of the pool gets a new one
    // (resetToInitialState), since to the user it is a new subsystem. It is not
    // serialized: a whole-diagram replay (project load, sandbox restore) builds
    // new nodes with new ids, and nothing keyed by the old ones may survive that.
    using NodeId = std::uint64_t;
    [[nodiscard]] NodeId getNodeId() const;
    // For ONE caller: ICoreStudioStateMachine::applyLocalTexts(), which rebuilds the
    // diagram with a whole replay and then hands each new node the id of the node
    // it replaces, so histories keyed by id survive. Anything else that sets an id
    // makes two subsystems answer to one.
    void adoptNodeId(NodeId id);
    // A fresh id, for a node that must give up the one it holds (a node in Trash
    // whose id a replayed node has just adopted).
    void reissueNodeId();

    // ======================= Getters ==============================

    ICoreSubsystemTreeNode* getParent() const;
    std::string getPath() const;
    std::string getName() const;
    [[nodiscard]] long long getCreatedTimeMs() const;
    [[nodiscard]] long long getLastModifiedTimeMs() const;
    // int getMaxSolverOrder() const;
    ICoreCanvasOriginAnchor* getCanvasOriginAnchor() const;

    std::vector<ICoreSubsystemTreeNode*> getChildrenTreeNodes() const;
    std::vector<ICoreBlock*> getChildrenBlocks() const;
    std::vector<ICoreLink*> getChildrenLinks() const;
    std::vector<ICoreCanvasArea*> getChildrenCanvasAreas() const;
    std::vector<ICoreCanvasTextBox*> getChildrenTextBoxes() const;
    std::vector<ICoreImage*> getChildrenImages() const;
    ICoreCanvas* getCanvasLoadedTo() const;
    ICoreBlock* getAssociatedSubsystemBlock() const;
    std::vector<ICoreBlock*> getAllGateBlocks() const;
    // std::unordered_map<int, std::vector<ICoreBlock*>> getBlocksToSolveMap() const;
    // std::vector<ICoreBlock *> getMatchingOrderBlocksToSolveList(const int &order) const;
     ICoreTreeNodeSolverEnvironment* getSolverEnvironment() const;

    bool isHomeNode() const;
    bool isRootNode() const;

    bool isHomeDescendantsNode() const;

    void printTree(const std::string& prefix) const;

    void printLinksReport() const;

    // The change-signal source, shared by every node. A static facade with no
    // instance of its own, exactly as ICoreProjectPreferences::notifier() is.
    static ICoreSubsystemTreeNotifier& notifier();

    void resetToInitialState(ICoreSubsystemTreeNode* parent = nullptr, const std::string& initName = "~auto");
    void kill();
    void setAlive();
    bool isAlive() const;

    ~ICoreSubsystemTreeNode();

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreSubsystemTreeNodeRegistry.h#

src/ICoreBlocks/ICoreModel/ICoreSubsystemTreeNodeRegistry.h

The live node with this id anywhere under Root (Home, Temp and Trash included), or nullptr. A walk of the tree, so O(nodes): for resolving a history's key, not for a per-frame loop.

ICoreSubsystemTreeNodeRegistry#

ICoreSubsystemTreeNodeRegistry.h:13 · class · 21 declaration(s)

class ICoreSubsystemTreeNodeRegistry {
public:

    static long initializeSubsystemTreeNodeRegistry();

    static ICoreSubsystemTreeNode* findTreeNodeByPath(const std::string& path);
    // The live node with this id anywhere under Root (Home, Temp and Trash
    // included), or nullptr. A walk of the tree, so O(nodes): for resolving
    // a history's key, not for a per-frame loop.
    static ICoreSubsystemTreeNode* findTreeNodeById(std::uint64_t nodeId);
    static ICoreVariablesSpace* findVariablesSpaceByPath(const std::string& path);
    static ICoreBlock* findBlockByPath(const std::string &path);
    static ICorePort* findPortByPath(const std::string &path);

    static const ICoreVariable* scanVariableSpacesForVariable(const ICoreBlock* parentBlock, const ICoreBlockConfigVariable *var);

    // ======================= Path forms ===========================
    // Tree paths are canonically rooted at the app name ("ICoreBlocks/Home/Motor"), but that
    // first segment is noise to the user -- and hidden altogether while navigation-root access
    // is off. These convert between the canonical form the lookups need and the form the UI
    // shows and accepts. Every find*ByPath() below normalises through toCanonicalPath(), so
    // callers may hand them either form.

    // Adds the "<AppName>/" root segment when it is missing and trims stray outer slashes:
    // "Home/Motor", "/Home/Motor" and "ICoreBlocks/Home/Motor" all canonicalise to the same
    // path. An empty input stays empty, which resolves to nothing.
    static std::string toCanonicalPath(const std::string& path);

    // Drops the root segment while navigation-root access is off, so the user reads
    // "Home/Motor". The Root node itself has no user-facing form and yields an empty string.
    // With access on the canonical path is handed back untouched.
    static std::string toDisplayPath(const std::string& path);

    // ================= Navigation root access =====================
    // Gates every user-facing route into the part of the tree that sits above Home: the Root
    // node itself and its non-Home children (Temp / Trash), which are internal
    // staging areas for scratch work and the recycle bin. Off by default, so the user only
    // ever sees Home and its descendants. Switch it on to expose the full tree unchanged.
    //
    // THE VALUE LIVES HERE. getNavigationRootTreeNode() below has to answer without asking
    // anybody, and this module may not name ICoreUserPreferences -- that is ICoreStudio, two
    // layers up (MODULE_LAYERING R5g).
    //
    // Making it survive a restart is the HOST's job: the setter raises
    // ICoreEditorHost::navigationRootAccessAllowedChanged(), and the studio host answers it by
    // writing the setting. A headless process has no host, so the flag is honoured for the
    // life of the run and nothing is written -- which is the right answer where there is no
    // user to have a preference.
    static bool isNavigationRootAccessAllowed();
    static void setNavigationRootAccessAllowed(bool allowed);

    // The other direction: the host telling the registry what the stored setting says, at
    // startup, on a reset to defaults, or when the panel changed it. Assigns and does NOT
    // raise the hook -- it is the answer to it, and re-raising would be a loop.
    static void syncNavigationRootAccessAllowed(bool allowed);

    // The node the navigation surfaces present as their top level: the Root node when access
    // is allowed, Home otherwise.
    static ICoreSubsystemTreeNode* getNavigationRootTreeNode();

    // True when the node may be shown to, or navigated to by, the user. A null node is never
    // visible, so callers can pass an unresolved lookup straight through.
    static bool isTreeNodeUserVisible(const ICoreSubsystemTreeNode* node);
    static bool isTreeNodePathUserVisible(const std::string& path);

    // ======================= Getters ==============================
    static ICoreSubsystemTreeNode* getRootTreeNode();
    static ICoreSubsystemTreeNode* getHomeTreeNode();
    static ICoreSubsystemTreeNode* getTempTreeNode();
    static ICoreSubsystemTreeNode* getTrashTreeNode();

    static bool isDebugMode();

    static void printGlobalTree();
};
};

ICoreBlock.h#

src/ICoreBlocks/ICoreModel/Block/ICoreBlock.h

The sink relaxation (design rows D4, T2.2). A port accepts only its own type by default; a block that DISPLAYS or RECORDS a signal rather than computing with it -- Scope, Display, Signal_Recorder, Terminator -- takes every kind that rides in the matrix (floating, integer, boolean) on every input. String and Bus are still refused: they ride beside the matrix (D2) and a sink that has not been taught to read the side carrier would draw a 1x1 zero and say nothing.

Called from the block's CONSTRUCTOR, like the port list it applies to. Nothing serializes it, so a block that only sets it on some paths comes back from a reload with the default.

It applies to the input ports this block has AND to every one it gains afterwards (T7.12) -- the user adds ports to a Scope from the config

ICoreBlock#

ICoreBlock.h:16 · class · pImpl · 93 declaration(s)

class ICoreBlock {
public:
    explicit ICoreBlock(ICoreSubsystemTreeNode* parent, const std::string &fullType);

    std::string generateUniqueName(const std::string& prefix) const;

    ICorePort* createNewPort(const std::string& portType, const std::string& initialPortDescription,
                                bool isOutputPort, const std::string& preferredFacing = "West");
    void deletePort(ICorePort* port);

    // The sink relaxation (design rows D4, T2.2). A port accepts only its own
    // type by default; a block that DISPLAYS or RECORDS a signal rather than
    // computing with it -- Scope, Display, Signal_Recorder, Terminator -- takes
    // every kind that rides in the matrix (floating, integer, boolean) on every
    // input. String and Bus are still refused: they ride beside the matrix (D2)
    // and a sink that has not been taught to read the side carrier would draw a
    // 1x1 zero and say nothing.
    //
    // Called from the block's CONSTRUCTOR, like the port list it applies to.
    // Nothing serializes it, so a block that only sets it on some paths comes
    // back from a reload with the default.
    //
    // It applies to the input ports this block has AND to every one it gains
    // afterwards (T7.12) -- the user adds ports to a Scope from the config
    // dialog and loadBlockConfig() re-creates them on load, and neither goes
    // through the constructor. The flag lives on the block for that reason.
    void setInputPortsAcceptAnyNumeric() const;

    // The same stickiness for a set this block chooses. The relaxation was
    // always a SET; setInputPortsAcceptAnyNumeric() is the numeric case of it,
    // and was the only one needed until a block wanted a set that is not the
    // numeric one -- Bus_Creator, whose element may be a String and therefore
    // cannot use the numeric set, and whose inputs are user-editable and so
    // need the stickiness for exactly T7.12's reason.
    //
    // An EMPTY list clears the relaxation, so every input goes back to
    // accepting only its own declared type.
    void setInputPortsAcceptedTypes(const std::vector<std::string>& acceptedTypeIds) const;

    // The ids of every kind that rides in the port's ICoreMatrix: floating,
    // integer, boolean, and the fixed-point family (FEATURES_TO_ADD.md BF14.2). The set setInputPortsAcceptAnyNumeric() hands out, named
    // once so a block needing the same set for ONE port (a Switch's data ports,
    // a Relational_Operator's inputs -- T7.6, T7.8) spells it the same way.
    static std::vector<std::string> numericSignalTypeIds();

    void deleteAllPorts();
    void acquirePortOwnership(ICorePort* port);
    void givePortOwnership(ICorePort* port);

    ICoreCanvasObjectState* getState() const;
    void updateToState(const ICoreCanvasObjectState* desiredState);

    void assignClonedProperties(ICoreBlock* originalBlock, bool clonePorts);

    void select();
    void deSelect();

    ICorePort* getPortByName(const std::string& portName) const;

    // ======================= Helpers ==============================

    bool checkNameValid(const std::string& nameToCheck);
    // bool isNameAvailable(const std::string& newName);

    // ======================= UI Manager ==============================

    void addToCanvas(ICoreCanvas* canvas);
    void freeFromCanvas();

    // Layer 4: canonical "remove me from the live model graph" — detach from parent container +
    // canvas/selection. Idempotent. Called by collectGarbage_Block so collection is self-sufficient.
    void detachFromModel();

    // ======================= Setters ==============================

    // Interactive rename: validates, and reports a rejected name to the user in a modal dialog.
    // Never call it for a name the code generated itself -- see setName_Raw_NoVerification.
    bool setName(const std::string& name);
    // Machine-set rename: no validation, no dialog. For names the code produced itself
    // (generateUniqueName, a captured state) where a modal error would stall a load, an undo or a
    // paste on a name the user never typed.
    void setName_Raw_NoVerification(const std::string& newName);
    void setCommentedOut(bool commentedOut);
    // The name label under the block is per-block state, not a view setting: it is
    // captured in the block's state and written to the recipe, so hiding it survives
    // undo/redo and a save/reload.
    void setNameLabelVisible(bool visible);
    void setParent(ICoreSubsystemTreeNode* newParent);
    void setAssociatedTreeNode(ICoreSubsystemTreeNode* ICoreSubsystemTreeNode);

    // ======================= Appearance ==============================
    //
    // A block type's default look: how big it is, what shape its frame is, and
    // whether the user may retype its port description labels. Every one of
    // these is declared once, per type, from the block's solver-environment
    // constructor in ICoreBlockLibrary.
    //
    // THEY LIVE HERE SO THE BLOCK LIBRARY DOES NOT HAVE TO NAME ICoreBlockView.
    // That library is ~113k lines across 302 files that contain no Qt
    // whatsoever -- until these calls, which reached through getBlockUI() into
    // a toolkit scene object and dragged the whole widget stack in behind them, in
    // 146 of those files, to say things as view-free as "a Terminator is 50x50".
    // Routed through the model instead, the statement stays where it belongs
    // and the dependency collapses to the one forwarding site in ICoreBlock.cpp.
    //
    // Forwarding, deliberately, not storage: setWidth() on the view also
    // resizes the frame and re-attaches the config UI, so these have to reach
    // it. What changed is who says the words, not what happens.
    //
    // double rather than the toolkit's real type because this header is
    // toolkit-free and stays that way; the forwarding site converts.
    void setWidth(double width);
    void setHeight(double height);
    void setRotation(double angleDegrees);
    void setAllowUserEditingPortDescLabels(bool allow);
    void setCircleFrame();
    void setTriangleFrame();
    void setFrameBackgroundColor(int red, int green, int blue);
    void setFrameMinimumWidth(double width);
    // Whether the block type's registered art is painted on this block's face.
    // Turn it off when the face carries something else -- a live readout. The
    // art stays REGISTERED either way: the library palette, the auto-inserter
    // button and ICoreRecipeInterpreter's block-type check all read it from
    // the factory, and a block type with no icon registered is not a valid
    // block type as far as a recipe is concerned.
    void setFaceIconVisible(bool visible);
    // Whether this block carries a readout PLATE on its face -- the bordered
    // ground setFaceText() writes into. Off by default: without this every
    // block on the canvas would wear an empty one. A block type that shows a
    // value turns it on once, and the plate then stays up for the block's whole
    // life, EMPTY between runs rather than vanishing. An empty readout is still
    // a readout; a block that loses its face looks broken.
    void setFaceReadoutVisible(bool visible);

    // ======================= Live face readout ==============================
    //
    // The text a block shows ON ITS OWN FACE, centred in the frame. Display is
    // what wanted it -- Simulink paints the value on the block, and this tree
    // had nowhere but the run log to put one -- and any block with a per-sample
    // value to show reaches it the same way.
    //
    // ⚠ SAFE TO CALL FROM THE SOLVER THREAD, and that is the whole point of it
    // being here rather than on the view. compute_h() runs on
    // ICoreModelSimulator's worker thread and the label is a scene item, so the
    // call stores the text and hops it to the GUI thread.
    //
    // Consecutive values COALESCE: at most one hop is ever in flight, and it
    // delivers whatever the latest text is when it lands. A solver stepping
    // thousands of times a second therefore costs the event loop a bounded
    // number of updates instead of one per sample, and the label still settles
    // on the final value of the run.
    void setFaceText(const std::string& text);
    // Hides the readout. Call it at the start of a run so a block does not open
    // one showing the last sample of the previous one.
    void clearFaceText();

    // ======================= Live face indicator colour =====================
    //
    // The block's own body colour, driven PER SAMPLE. Dashboard/Lamp is what
    // wanted it -- a lamp IS its colour, there is no number to put on a face --
    // and any block whose state reads better as a colour than as a value reaches
    // it the same way.
    //
    // ⚠ SAFE TO CALL FROM THE SOLVER THREAD, exactly as setFaceText() is and for
    // exactly that reason: compute_h() runs on ICoreModelSimulator's worker
    // thread and the frame is a scene item, so the call stores the colour and
    // hops it to the GUI thread. setFrameBackgroundColor() above does NOT do
    // this -- it writes the scene item directly, which is why it is only ever
    // called from a constructor (Mux, Demux).
    //
    // Consecutive colours COALESCE, as consecutive face texts do: at most one
    // hop is in flight and it delivers whatever the latest colour is when it
    // lands, so a solver stepping thousands of times a second costs the event
    // loop a bounded number of updates and the frame still settles on the run's
    // final colour. Components outside 0..255 are clamped.
    void setFaceIndicatorColor(int red, int green, int blue);

    // ======================= Live face picture ==============================
    //
    // A picture of this block instance's own, painted IN PLACE of its type's
    // art and switched PER SAMPLE -- MultiStateImage shows one picture per
    // state (FEATURES_TO_ADD.md BF22.4). Takes the path as a config stores it
    // (ICoreBlockConfigVariable::setIsImagePath): "Images/<id>.<ext>" relative
    // to the project, or an absolute file name. "" goes back to the type's art.
    //
    // ⚠ SAFE TO CALL FROM THE SOLVER THREAD and COALESCED, exactly as
    // setFaceText() is: at most one hop is in flight, and it delivers the
    // latest path when it lands. Each file is read once and kept by the view,
    // so switching back and forth between a few pictures costs no file reads.
    void setFacePicture(const std::string& storedPath);

    // ======================= Live face drawing ==============================
    //
    // A drawing of this block's own on its face -- arcs, ticks, needles, text
    // and filled paths, as plain data (ICoreFaceDrawing) -- which an instrument
    // dial rebuilds from each sample (FEATURES_TO_ADD.md BF5.3, owner decision
    // D5). The view paints it (BF5.4); the block only says what is there.
    //
    // ⚠ SAFE TO CALL FROM THE SOLVER THREAD and COALESCED, exactly as
    // setFaceText() is: at most one hop is in flight, and it delivers the latest
    // drawing when it lands. A drawing with problems() is dropped and logged,
    // not handed to the view half-valid.
    void setFaceDrawing(ICoreFaceDrawing drawing);
    // Takes the drawing off the face. Call it at the start of a run, as
    // clearFaceText(), so a dial does not open showing the last run's value.
    void clearFaceDrawing();

    // ======================= Face click ==============================
    //
    // A click on this block's face (FEATURES_TO_ADD.md BF22.1): a left-button press and
    // release on a block drawn on a canvas, which moved it less than a selection click
    // allows. The canvas still selects the block exactly as for any click, and a drag
    // still moves it and clicks nothing; a right click, a double click's second press
    // and a block in the library are not clicks. The handler runs on the GUI thread once
    // the release has been handled, so it may open a window or evaluate a console line.
    // Clicks are allowed during a run (decision D18). Every block has none until it sets
    // one -- in its constructor, as it declares its ports; it is not saved, and a
    // recycled block has none.
    void setFaceClickHandler(std::function<void()> handler);
    [[nodiscard]] bool hasFaceClickHandler() const;
    // Runs the handler; what the canvas calls on a click. False when there is none, or
    // the block is no longer alive.
    bool faceClicked();

    // ======================= Getters ==============================

    ICoreSubsystemTreeNode* getParent() const;
    std::string getName() const;
    std::string getType();
    std::string getFullType();
    std::string getPath() const;

    ICoreBlockView* getBlockUI() const;

    std::vector<ICorePort*> getPorts() const;

    std::vector<ICorePort*> getInputPorts() const;
    std::vector<ICorePort*> getOutputPorts() const;

    void setGateBlock();
    void setSubsystemBlock();
    void setScopeBlock();
    // A sink that consumes signals without charting them (Signal Recorder). Kept
    // apart from setScopeBlock() because that one attaches a chart; what the two
    // share is being a terminal the verifier has to record at "Output Gates and
    // Sink Blocks", which is what isSinkBlock() answers for.
    void setSinkBlock();
    bool isGateBlock() const;
    bool isSubsystemBlock() const;
    bool isScopeBlock() const;
    bool isSinkBlock() const;

    // An ATOMIC subsystem is Simulink's TreatAsAtomicUnit: its contents run as one unit, and its own Sampling
    // Time (s), when positive, is Simulink's SystemSampleTime -- every block
    // inside that inherits (-1) runs at it, and a block at any other period is
    // refused. The flag is the Subsystem block's config of this name, an
    // Off/On option; any other block, and Home, answers false.
    static const std::string CONFIG_TREAT_AS_ATOMIC_UNIT;
    bool isAtomicSubsystem() const;
    // The subsystem that imposes a period on this block, and that period:
    // walking up, the first atomic subsystem with a positive Sampling Time (s)
    // answers; an atomic one at -1 inherits from above, and a plain one is
    // passed through, as Simulink ignores SystemSampleTime on a virtual
    // subsystem. {nullptr, 0} when none does.
    std::pair<const ICoreBlock*, double> enclosingPeriodicAtomicSubsystem() const;
    // A REFERENCED subsystem (FEATURES_TO_ADD.md BF13.2) names an .icore recipe
    // file in the project folder, by a project-relative path, in the Subsystem
    // block's config of this name; editing one instance edits the file, and so
    // every instance (owner decision D10). ICoreReferencedFiles (ICoreStudio) owns
    // the file. The path as written, or empty: on any other block, on Home, and on
    // an ordinary subsystem.
    static const std::string CONFIG_REFERENCED_FILE;
    std::string referencedFile() const;
    // A MODEL block (FEATURES_TO_ADD.md BF13.4) is a referenced subsystem whose
    // "Reference Type" is Model rather than Subsystem: Simulink's Model block
    // (ModelReference), where a Subsystem Reference is a plain subsystem. It is
    // atomic by nature (isAtomicSubsystem() answers true whatever its own flag
    // says), so its Sampling Time (s) is its own rate, and its "Model Arguments"
    // -- `K=2, c=[1 2]`, Simulink's InstanceParameters -- give each instance its own
    // value for a name the blocks inside it read: a name an instance argues
    // shadows a global variable of that name inside it, and an inner instance's
    // shadows an outer's (ICoreSubsystemTreeNodeRegistry::scanVariableSpacesForVariable).
    static const std::string CONFIG_REFERENCE_TYPE;
    static const std::string CONFIG_MODEL_ARGUMENTS;
    bool isModelReference() const;
    // The instance's arguments in the order written, each `name=value`, split at
    // top-level commas or semicolons (a bracketed value keeps its own). Empty on
    // anything but a Model block.
    std::vector<std::pair<std::string, std::string>> modelArguments() const;
    // The parser modelArguments() uses, for a text that is not on a block (the
    // Simulink bridge reads and writes the config's text).
    static std::vector<std::pair<std::string, std::string>> splitModelArguments(const std::string& text);

    bool isSelected() const;
    bool isCommentedOut() const;

    // VARIANT PRUNING (FEATURES_TO_ADD.md BF3.3). A variant block marks the ports its
    // inactive choices sit on, in its config load, every build; the build then takes
    // out every block an inactive port makes inactive (ICoreModelBuild::
    // pruneInactiveVariants) exactly as it takes out a commented-out block. Neither is
    // saved: an inactive port is re-decided at every build and a pruned block is a
    // fact about one build, unlike isCommentedOut(), which is the user's and is.
    // isExcludedFromRun() is the one question the run and the export ask.
    void setInactiveVariantPorts(const std::vector<const ICorePort*>& ports);
    bool isInactiveVariantPort(const ICorePort* port) const;
    void setPrunedByVariant(bool pruned);
    bool isPrunedByVariant() const;
    bool isExcludedFromRun() const;
    bool isNameLabelVisible() const;
    bool isDebugActive() const;

    std::string getClassID() const;

    ICoreSubsystemTreeNode* getAssociatedTreeNode() const;

    ICoreBlockSubsystemGate* getAssociatedSubsystemGate() const;
    ICoreChart* getAttachedChart() const;

    ICoreCanvas* getLoadedToCanvas() const;

    ICoreBlockConfigurator* getBlockConfigurator() const;
    ICoreBlockSolverEnvironment* getSolverEnvironment() const;

    void assignSolverEnvironment(std::unique_ptr<ICoreBlockSolverEnvironment> newEnvironment);

    void setTrashOrder(const int newTrashOrder);
    int getTrashOrder() const;
    void increaseTrashOrder();
    void decreaseTrashOrder();

    void resetToInitialState(ICoreSubsystemTreeNode* parent, const std::string &type);
    void kill();
    void setAlive();
    bool isAlive() const;

    ~ICoreBlock();

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreFaceDrawing.h#

src/ICoreBlocks/ICoreModel/Block/ICoreFaceDrawing.h

ICoreFaceDrawingColor#

ICoreFaceDrawing.h:34 · struct · 0 declaration(s)

A picture a block draws on its own face, as PLAIN DATA: a list of primitives in a box of its own, which the block view scales into the block's face.

struct ICoreFaceDrawingColor {
public:
    int red = 0;
    int green = 0;
    int blue = 0;
    int alpha = 255;
};
};

ICoreFaceDrawingGradientStop#

ICoreFaceDrawing.h:41 · struct · 0 declaration(s)

struct ICoreFaceDrawingGradientStop {
public:
    double offset = 0;   // 0..1 along the gradient
    ICoreFaceDrawingColor color;
};
};

ICoreFaceDrawingPaint#

ICoreFaceDrawing.h:47 · struct · 1 declaration(s)

What fills an area or strokes a line.

struct ICoreFaceDrawingPaint {
public:
    enum class Kind { None, Solid, Linear, Radial };
    Kind kind = Kind::None;
    ICoreFaceDrawingColor color;              // Solid
    double x1 = 0, y1 = 0, x2 = 0, y2 = 0;    // Linear: from (x1, y1) to (x2, y2)
    double cx = 0, cy = 0, radius = 0;        // Radial: centre and radius
    std::vector<ICoreFaceDrawingGradientStop> stops;          // Linear and Radial, by offset
};
};

ICoreFaceDrawingTransform#

ICoreFaceDrawing.h:59 · struct · 0 declaration(s)

Rotate by rotation degrees about (pivotX, pivotY), then translate by (dx, dy).

struct ICoreFaceDrawingTransform {
public:
    double rotation = 0;
    double pivotX = 0, pivotY = 0;
    double dx = 0, dy = 0;
};
};

ICoreFaceDrawingPathStep#

ICoreFaceDrawing.h:66 · struct · 0 declaration(s)

One step of a Path, in drawing coordinates.

struct ICoreFaceDrawingPathStep {
public:
    enum class Verb { MoveTo, LineTo, CubicTo, Close };
    Verb verb = Verb::MoveTo;
    double x = 0, y = 0;                      // the point reached
    double c1x = 0, c1y = 0, c2x = 0, c2y = 0;   // CubicTo's two control points
};
};

ICoreFaceDrawing#

ICoreFaceDrawing.h:74 · struct · nested Primitive · 6 declaration(s)

struct ICoreFaceDrawing {
public:
    // Where it comes from, kept out of the banner above because that is
    // published: FEATURES_TO_ADD.md BF5.3, owner decision D5; the primitive
    // set is the one BF5.1 measured the aerolibhmi faces need; BF5.4 paints it.
    using Color = ICoreFaceDrawingColor;
    using GradientStop = ICoreFaceDrawingGradientStop;
    using Paint = ICoreFaceDrawingPaint;
    using Transform = ICoreFaceDrawingTransform;
    using PathStep = ICoreFaceDrawingPathStep;

    enum class TextAlign { Left, Center, Right };

    // One primitive. Which fields it reads depends on `op`:
    //   Circle      cx, cy, radius
    //   Rect        x, y, width, height (before its transform)
    //   Arc         cx, cy, radius, startAngle, sweepAngle -- stroked only; a
    //               sweep past 180 is the large arc
    //   Segment     cx, cy, radius, startAngle, sweepAngle -- the arc CLOSED BY
    //               ITS CHORD, filled (the artificial horizon's ground)
    //   Path        steps
    //   Text        x, y (the baseline's anchor), text, fontSize, align, bold;
    //               drawn with `fill`
    //   BeginGroup  opens a group: its transform applies to every primitive up
    //               to the matching EndGroup, after each one's own
    //   EndGroup    closes the innermost group
    // Every primitive but EndGroup takes its own `transform` too.
    struct Primitive {
        enum class Op { Circle, Rect, Arc, Segment, Path, Text, BeginGroup, EndGroup };
        Op op = Op::Circle;
        double cx = 0, cy = 0, radius = 0;
        double x = 0, y = 0, width = 0, height = 0;
        double startAngle = 0, sweepAngle = 0;
        std::vector<PathStep> steps;
        std::string text;
        double fontSize = 12;
        TextAlign align = TextAlign::Center;
        bool bold = false;
        Paint fill;
        Paint stroke;
        double strokeWidth = 1;
        Transform transform;
    };

    double width = 300;
    double height = 300;
    std::vector<Primitive> primitives;

    // ---- Building one, in painting order (later primitives paint over) ----
    // Each returns the primitive it appended, to set what the arguments do not.
    static Paint solid(int red, int green, int blue, int alpha = 255);
    Primitive& addCircle(double cx, double cy, double radius, const Paint& fill,
                         const Paint& stroke = Paint(), double strokeWidth = 1);
    Primitive& addRect(double x, double y, double width, double height, const Paint& fill,
                       const Transform& transform = Transform());
    Primitive& addArc(double cx, double cy, double radius, double startAngle, double sweepAngle,
                      const Paint& stroke, double strokeWidth);
    Primitive& addSegment(double cx, double cy, double radius, double startAngle, double sweepAngle,
                          const Paint& fill);
    Primitive& addPath(std::vector<PathStep> steps, const Paint& fill, const Paint& stroke = Paint(),
                       double strokeWidth = 1);
    Primitive& addText(double x, double y, const std::string& text, double fontSize, const Paint& fill,
                       TextAlign align = TextAlign::Center);
    Primitive& beginGroup(const Transform& transform);
    void endGroup();

    // What is wrong with it, one line per problem, naming the primitive by
    // index: a group left open or closed twice, a non-finite number, a negative
    // radius or size, an empty box. Empty when it can be painted as written.
    // ICoreBlock::setFaceDrawing() drops a drawing with problems rather than
    // hand a half-valid one to the view, so a block author should call this.
    [[nodiscard]] std::vector<std::string> problems() const;
};
};

ICoreBlockConfigVariable.h#

src/ICoreBlocks/ICoreModel/Block/BlockConfig/ICoreBlockConfigVariable.h

ICoreBlockConfigVariable deepCopy() const;

void linkToVariablesSpace(ICoreVariablesSpaceVariable* variablesSpaceVariable); void unlinkFromVariablesSpace();

ICoreVariablesSpaceVariable* getLinkedVariablesSpaceVariable() const;

Declares no class of its own — see the file.

ICoreBlockConfigurator.h#

src/ICoreBlocks/ICoreModel/Block/BlockConfig/ICoreBlockConfigurator.h

Designates one (private) config variable as user-editable source code: the config dialog toolbar shows an "Edit Code" button bound to it, and the recipe serializer persists it (base64) despite it being private.

ICoreBlockConfigurator#

ICoreBlockConfigurator.h:7 · class · pImpl · 31 declaration(s)

class ICoreBlockConfigurator {
public:
    explicit ICoreBlockConfigurator(ICoreBlock* parentBlock);

    ICoreBlockConfigVariable* createNewVariable(const std::string& initName, const std::string& initValue, const bool& isPrivate = false);
    bool isVarNameUnique(const std::string& nameToCheck) const;

    void clearAllVariables_WithoutUIEntries();

    void requestToShowConfigUI();

    void hideConfigUI();
    void deleteConfigUI();

    void assignClonedProperties(ICoreBlockConfigurator* originalBlockConfigurator) const;

    void setIsPinnedToCanvas(bool newIsPinned) const;

    void setBlockDescription(const std::string& newBlockDescription);

    void setAllowUserEditingNumberOfInputPorts(const bool& newProperty);
    void setAllowUserEditingNumberOfOutputPorts(const bool& newProperty);

    ICoreBlockConfigView* getConfigUI() const;

    bool getIsInputPortsNumberPrivate() const;
    bool getIsOutputPortsNumberPrivate() const;

    void setDefaultOutputPortDescText(const std::string &newValue);
    void setDefaultInputPortDescText(const std::string &newValue);

    std::string getDefaultOutputPortDescText() const;

    std::string getDefaultInputPortDescText() const;

    // Designates one (private) config variable as user-editable source code:
    // the config dialog toolbar shows an "Edit Code" button bound to it, and
    // the recipe serializer persists it (base64) despite it being private.
    void setCodeEditorVariableName(const std::string& configVarName);
    std::string getCodeEditorVariableName() const;  // "" = block has no code editor

    // Which language the code editor window opens the variable in (editor
    // widget + highlighter + window title). Python unless the block says so.
    enum class CodeEditorLanguage { Python, C };
    void setCodeEditorLanguage(CodeEditorLanguage newLanguage);
    CodeEditorLanguage getCodeEditorLanguage() const;

    std::string getDescriptionText() const;
    std::vector<ICoreBlockConfigVariable*> getAllVariables();
    ICoreBlock* getParentBlock() const;

    ICoreBlockConfigVariable* getConfigVariable(const std::string& name) const;

    void resetToInitialState(ICoreBlock* parentBlock);
    void kill();
    void setAlive();
    bool isAlive() const;

    ~ICoreBlockConfigurator();

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreBlockReferences.h#

src/ICoreBlocks/ICoreModel/Block/BlockConfig/ICoreBlockReferences.h

ICoreBlockReferences#

ICoreBlockReferences.h:50 · class · nested Suspension · 5 declaration(s)

ONE BLOCK NAMING ANOTHER (FEATURES_TO_ADD.md BF11.2, owner decision D9 (a)).

class ICoreBlockReferences {
public:
    // The block `path` names, or nullptr with the reason in `refusal`. Accepts
    // the displayed form ("Home/Plant/Gain") and the canonical one
    // ("ICoreBlocks/Home/Plant/Gain"). Refuses an empty path, a path that names
    // no block, and a block that is not in the diagram (in Trash).
    static ICoreBlock* resolve(const std::string& path, std::string* refusal = nullptr);

    // The block that `referrer`'s reference config `configName` names. Refuses,
    // as above, and also a config that does not exist or is not a reference, and
    // a reference to the referrer itself.
    static ICoreBlock* resolve(const ICoreBlock* referrer, const std::string& configName,
                               std::string* refusal = nullptr);

    // One line per reference config of `blocks` that does not resolve, naming
    // the block, the config and the path. What a run refuses to start on.
    static std::vector<std::string> unresolvedReferences(const std::vector<ICoreBlock*>& blocks);

    // ---- State access by path (FEATURES_TO_ADD.md BF15.3) ----
    //
    // A config marked ICoreBlockConfigVariable::setStateAccess names a STATE
    // OWNER. R2026a does not order a State Reader or Writer against its owner at
    // all: it refuses every placement in which the two could run in an order
    // the model does not state, and accepts only placements that state one --
    // Function-Call Split, an If or Switch Case over action subsystems, an
    // Initialize or Terminate Function (BF15.1, measured). So the build's job
    // is to refuse, in R2026a's words and before the first step:
    //
    //   - an owner that cannot own state
    //     (Simulink:blocks:StateReaderOrWriterInvalidStateOwner);
    //   - an accessor in the same non-virtual hierarchy as its owner -- the same
    //     nearest atomic subsystem, or both under none
    //     (Simulink:blocks:StateAccessorAndOwnerInSameCompInfo);
    //   - an accessor in another non-virtual hierarchy with no stated order
    //     between the two (Simulink:blocks:StateAccessorAndOwnerInNonExpFcnMdlSameTsError),
    //     which today is every such placement: ICore has no function-call,
    //     action or Initialize/Terminate subsystem yet;
    //   - two accessors of one owner in one non-virtual hierarchy
    //     (Simulink:blocks:StateAccessorBlocksInSameCompInfo).
    //
    // One line per refusal, naming the blocks. An unresolved path is not here:
    // unresolvedReferences() refuses it first.
    static std::vector<std::string> stateAccessRefusals(const std::vector<ICoreBlock*>& blocks);

    // The state owner `accessor`'s state-access config `configName` names, or
    // nullptr with the reason in `refusal`: what a State Reader or Writer reads
    // its owner through once the build has accepted the placement.
    static ICoreBlock* stateOwner(const ICoreBlock* accessor, const std::string& configName,
                                  std::string* refusal = nullptr);

    // The block at `oldPath` is now at `newPath`: rewrite every reference to it,
    // or to anything inside it, across the whole diagram. Does nothing inside a
    // Suspension, and nothing unless BOTH paths are inside Home. Returns how many
    // configs it rewrote.
    static int followPathChange(const std::string& oldPath, const std::string& newPath);

    // False inside a Suspension.
    static bool isFollowingPathChanges();

    // Stops followPathChange for as long as it lives. Nests.
    class Suspension {
    public:
        Suspension();
        ~Suspension();
        Suspension(const Suspension&) = delete;
        Suspension& operator=(const Suspension&) = delete;
    };
};
};

ICoreActionDriver.h#

src/ICoreBlocks/ICoreModel/Block/GateBlock/ICoreActionDriver.h

ICoreActionDriver#

ICoreActionDriver.h:30 · class · nested ExportNode · 4 declaration(s)

What an If or a Switch Case block CHOOSES on a step: which one of the action subsystems it drives runs.

class ICoreActionDriver {
public:
    // Whether `expression` parses for an If with `numInputs` inputs; `why` names
    // the refusal when not. Input sizes are not known here, so an index is
    // checked when the expression is evaluated.
    static bool checkIfExpression(const std::string& expression, int numInputs, std::string& why);

    // The value of `expression` over `inputs` (u1 is inputs[0]), or false with
    // `why` when it cannot be evaluated.
    static bool evaluateIfExpression(const std::string& expression, const std::vector<ICoreMatrix>& inputs,
                                     double& value, std::string& why);

    // The output an If chooses: the index of the first true expression among
    // `expressions` (If first, then the Elseifs), the next index (the else
    // output) when none is and `showElse`, or -1 for none. -2 with `why` when an
    // expression cannot be evaluated.
    // The If's expressions in evaluation order: its If expression, then each
    // Elseif expression -- a comma-separated list split at EVERY comma, as R2026a
    // splits it (measured for BF8.5: 'max(u1, u2) > 0.1, u2 > 0' and
    // 'u1(2,1) > 0, ...' are syntax errors there), so a comma inside an Elseif's
    // parentheses leaves a fragment that checkIfExpression refuses by name. The
    // If expression itself is one expression, commas and all.
    static std::vector<std::string> ifExpressions(const std::string& ifExpression, const std::string& elseifs);

    static int chooseIf(const std::vector<std::string>& expressions, bool showElse,
                        const std::vector<ICoreMatrix>& inputs, std::string& why);

    // The case values in R2026a's notation, {1,[2 3]}: one list per case. Empty
    // on a malformed one -- ICoreSubsystemControls refuses those before a run.
    static std::vector<std::vector<long long>> caseValues(const std::string& conditions);

    // The output a Switch Case chooses for `input`: the index of the first case
    // listing trunc(input), else the default (index cases.size()) when
    // `showDefault`, else -1.
    static int chooseCase(const std::vector<std::vector<long long>>& cases, bool showDefault, double input);

    // AN IF EXPRESSION FOR CODE EXPORT: its tree with everything a generated core
    // cannot look up resolved now -- a variables-space name and true/false to their
    // numbers, `end` and every subscript to a fixed element -- so a generator only
    // prints it. Each node is one scalar double; an input is read element by
    // element (`input`, `row`, `col`, 0-based). `op` is the operator ("+", "-",
    // "*", "/", "^", "==", "~=", "<", "<=", ">", ">=", "&", "|", "&&", "||", and
    // "-", "+", "~" unary) or the function name, and every node evaluates exactly
    // as evaluateIfExpression does it: a comparison or a logical operator gives 1
    // or 0, mod(x,0) and rem(x,0) give x, round goes half away from zero,
    // sign(NaN) is 0, min/max keep the first argument unless the second is
    // strictly smaller/larger. One-argument min/max of a vector input is unrolled
    // into two-argument ones.
    struct ExportNode {
        enum class Kind { Number, Element, Unary, Binary, Call };
        Kind kind = Kind::Number;
        double number = 0.0;
        int input = 0;
        int row = 0;
        int col = 0;
        std::string op;
        std::vector<ExportNode> args;
    };
    // `shapes` holds each input's rows and columns. False with `why` for what
    // evaluateIfExpression would refuse, and for a subscript that depends on an
    // input's value or a whole vector input outside min/max, which a generated
    // core cannot index.
    static bool exportIfExpression(const std::string& expression, const std::vector<std::pair<int, int>>& shapes,
                                   ExportNode& out, std::string& why);
};
};

ICoreBlockSubsystemGate.h#

src/ICoreBlocks/ICoreModel/Block/GateBlock/ICoreBlockSubsystemGate.h

ICoreBlockSubsystemGate#

ICoreBlockSubsystemGate.h:8 · class · pImpl · 11 declaration(s)

class ICoreBlockSubsystemGate {
public:
    explicit ICoreBlockSubsystemGate(ICoreBlock* parentBlock);

    void deleteParentSubsystemLinkedPort(ICorePort* gateBlockPort);
    void createParentSubsystemLinkedPort(ICorePort* gateBlockPort);

    std::string getUniquePortDescLabelText(const bool& isOutputPort) const;

    void regenerateAllParentSubsystemPortsAfterGateBlockMigration();

    void deleteAllPortsAtBothBlocks();

    void resetToInitialState(ICoreBlock* parentBlock);
    void kill();
    void setAlive();
    bool isAlive() const;

    ~ICoreBlockSubsystemGate();

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreMergeRule.h#

src/ICoreBlocks/ICoreModel/Block/GateBlock/ICoreMergeRule.h

ICoreMergeRule#

ICoreMergeRule.h:27 · class · nested Choice · 6 declaration(s)

Simulink's Merge rule, live: which of a Merge block's inputs it passes on this step.

class ICoreMergeRule {
public:
    // Whether the output port `source` was written on this step: its block ran.
    // A Subsystem block's output is written by the output gate inside, so it is
    // written exactly when that subsystem ran. Asks ICoreSubsystemActivity, so it
    // answers for the step the solver is on, or the last one of a finished run.
    static bool writtenThisStep(const ICorePort* source);

    // Whether `source` sits in -- or is the output of -- a conditionally executed
    // subsystem: what a Merge source must be.
    static bool isConditionalSource(const ICorePort* source);

    struct Choice {
        int input = -1;     // the input to pass on, or -1 to hold
        int writers = 0;    // how many inputs were written this step
    };
    // The choice for this step over the Merge's inputs' SOURCES, in input order
    // (a null entry is an unconnected input, never written).
    static Choice choose(const std::vector<const ICorePort*>& sources);

    // The Merge block's type, "Control_Systems/Signal_Routing/Merge".
    static const std::string MERGE_TYPE;
    static bool isMerge(const ICoreBlock* block);

    // The conditionally executed subsystem that decides whether `source` is
    // written, as seen from the level `merge` sits in: the innermost subsystem
    // between the writer and that level which an Enable, Trigger or Action port
    // controls. Null when there is none, or when the innermost one is iterated.
    static const ICoreSubsystemTreeNode* deciderOf(const ICorePort* source, const ICoreBlock* merge);

    // What stops a run, and an export, of the Merge `merge`, one line per
    // problem, as R2026a refuses them: an input whose source is not the output
    // of a conditionally executed subsystem (InvMergeConnNonCondSubsysSrc; an
    // iterated one is no exception), a source Outport that resets while
    // disabled (InvMergeConnSrcResetOnDisable), and a source that also feeds
    // another block (InvMergeConnMultDst).
    static std::vector<std::string> refusals(const ICoreBlock* merge);
};
};

ICoreSubsystemActivity.h#

src/ICoreBlocks/ICoreModel/Block/GateBlock/ICoreSubsystemActivity.h

ICoreSubsystemActivity#

ICoreSubsystemActivity.h:51 · class · nested PassScope · 24 declaration(s)

WHETHER A CONDITIONALLY EXECUTED SUBSYSTEM RUNS THIS STEP (FEATURES_TO_ADD.md BF2.3).

class ICoreSubsystemActivity {
public:
    // Forgets every decision and edge memory: the start of a run.
    static void beginRun();

    // Decides whether the conditional subsystem whose Subsystem block is `face`
    // runs at `t`, from the control values on its face ports as they stand, and
    // applies what the decision implies (a re-seed, initial outputs). Called
    // from the face's own solve, after it has read its inputs; a subsystem with
    // no Enable, Trigger or Reset port is left alone. Deciding again at the same
    // time repeats the same decision.
    static void decide(const ICoreBlock* face, double t);

    // False when any subsystem enclosing `block` is conditional and inactive at
    // its last decision. True for every block of a model with none, cheaply.
    static bool runs(const ICoreBlock* block);

    // What the Trigger port block inside `face`'s subsystem outputs, as R2026a
    // does: per element, +1 if its last trigger event rose and -1 if it fell (0
    // before any), held between events. `value` arrives shaped like the port;
    // false, untouched, before the subsystem's first decision or on a shape the
    // memory does not have.
    static bool triggerOutput(const ICoreBlock* face, ICoreMatrix& value);

    // ---- Iterated subsystems: For and While (FEATURES_TO_ADD.md BF9.3) ----
    //
    // An iterated subsystem's run executes k times inside one step, and what is
    // seen outside is the last pass. Its own Subsystem block drives the passes
    // from its slot: decide() hands the face to the RUN DRIVER, which ICoreSimulation
    // installs because only it knows the build's ordered block list; the driver
    // asks beginPasses(), solves the run once per pass inside a PassScope, and asks
    // anotherPass() after each. Every block inside answers mayRunNow() false
    // unless its own iterated subsystem is the one driving, so the main loop never
    // solves it a second time, and nested iterators each drive their own run. As
    // R2026a measured them (§F.BF9):
    //   - For: N passes, the Iteration limit or the input truncated toward zero;
    //     N = 0 runs nothing and the outputs hold. The iteration output reads the
    //     pass, one- or zero-based.
    //   - While: the condition is tested AFTER each pass; a while loop with a
    //     false IC runs nothing (outputs hold), do-while always runs once;
    //     Maximum number of iterations stops it silently, -1 is no limit, 0 runs
    //     nothing.
    //   - Reset states = reset re-seeds once per step, before the first pass,
    //     never between passes.
    using RunDriver = void (*)(const ICoreBlock* face, double t);
    static void setRunDriver(RunDriver driver);

    // The nearest For-, While- or For Each-iterated subsystem above `block`, or
    // function-call subsystem (BF7.3), or message-triggered one (BF4.6), or nullptr.
    static const ICoreSubsystemTreeNode* iteratedSubsystemOf(const ICoreBlock* block);
    static bool mayRunNow(const ICoreBlock* block);
    // True on the second and later passes: the same t again, so a block's
    // fixed-step budget check does not apply.
    static bool isRepeatPass();

    // For the driver. beginPasses() plans the step and applies the reset; false
    // when the step runs no pass. anotherPass() is asked after pass `completed`.
    static bool beginPasses(const ICoreBlock* face, double t);
    static bool anotherPass(const ICoreBlock* face, int completed);
    // The pass running now in `node`, 1-based; 0 outside a pass.
    static int currentPass(const ICoreSubsystemTreeNode* node);
    // After the last pass: a For Each writes each face output, its slices
    // joined. Nothing for For and While, whose last pass already wrote them.
    static void endPasses(const ICoreBlock* face);

    // ---- For Each (FEATURES_TO_ADD.md BF9.4) ----
    //
    // A For Each subsystem is driven the same way, one pass per slice of its
    // partitioned inputs (ICoreSubsystemControls::forEachSliceCount), and each
    // pass is a slice with its own copy of every state. PassScope loads that copy
    // into every block the For Each owns -- every block under it that no nested
    // For Each owns instead -- and into the activity memory of every conditional
    // or iterated subsystem among them, and stashes it again when the pass ends
    // (ICoreBlockSolverEnvironment::stashSliceState). A copy is keyed by the
    // slice and the slices of every For Each around it, so a nested For Each
    // keeps one per pair. The input gates read the slice (sliceInput) and the
    // output gates hand theirs over (collectSlice) instead of writing the face.
    //
    // R2026a's For Each is not an iterator for the time rules (BF9.1): it takes
    // continuous blocks, a Discrete-Time Integrator and an explicit rate, and
    // each slice's state keeps its own clock, so its passes are not repeats.
    //
    // `value` is what the input gate paired with `facePort` would hand on; it is
    // replaced by the current slice's part. False, untouched, outside a For Each
    // pass or for an input it does not partition.
    static bool sliceInput(const ICorePort* facePort, ICoreMatrix& value);
    // True when `value` was kept as the current slice's part of face output
    // `facePort`; the gate then leaves the face alone.
    static bool collectSlice(const ICorePort* facePort, const ICoreMatrix& value);
    // The key of the slice running now ("" outside any For Each): each
    // enclosing For Each's slice, outermost first, each followed by '/'.
    static std::string currentSliceKey();
    // Counts the runs (beginRun), so a slice copy made in an earlier run is
    // never loaded in a later one.
    static unsigned long long runCount();

    // ---- Function-call subsystems (FEATURES_TO_ADD.md BF7.3) ----
    //
    // A function-call subsystem (its Trigger port's Trigger type is
    // function-call) leaves its parent's order: its blocks run only when a
    // caller calls it, at the caller's position, once per call, as R2026a
    // measured (§F.BF7). A block that writes a function-call output -- not a
    // gate and not a face, which only carry one across a boundary -- dispatches
    // once it has written them: the calls on each line (the sum of its
    // elements, each truncated toward zero) go to the function-call faces the
    // line reaches, through plain subsystem boundaries. The CALL DRIVER, which
    // ICoreSimulation installs, refreshes the face's inputs and solves the run
    // once per call inside a PassScope: states advance per call, the outputs
    // hold between calls, and before the first call they read the output
    // gates' Initial output. A call's pass is never budget-checked, since a
    // call comes when its caller says and not on the subsystem's own period.
    // iteratedSubsystemOf() and mayRunNow() treat a called subsystem as the
    // iterators' runs are treated: only its driver solves its blocks.
    using CallDriver = void (*)(const ICoreBlock* face, long long calls, double t);
    static void setCallDriver(CallDriver driver);
    static void dispatchCalls(const ICoreBlock* caller, double t);
    // The function-call faces ONE call on `source` runs, in the order it runs
    // them: through plain boundaries, and through a Function-Call Split output
    // by output, depth first, as R2026a does (BF7.1: DS = 1234). A face reached
    // twice is listed twice. What the build orders a Split's subsystems by, and
    // what code export checks its order against.
    static std::vector<const ICoreBlock*> facesCalledFrom(const ICorePort* source);
    // Whether `node` is a function-call subsystem.
    static bool isCalled(const ICoreSubsystemTreeNode* node);
    // Whether `node` is an Initialize, Terminate, Reset or Reinitialize Function
    // (FEATURES_TO_ADD.md BF25.2). It leaves the step as a called subsystem
    // does; ICoreSimulation solves its run once through the call driver,
    // Initialize before the first output and Terminate after the last step.
    static bool isEventSubsystem(const ICoreSubsystemTreeNode* node);
    // For the driver, before the first pass: the subsystem has executed, so its
    // face no longer holds its outputs at their Initial output.
    static void beginCalls(const ICoreBlock* face);

    // ---- Message-triggered subsystems (FEATURES_TO_ADD.md BF4.6) ----
    //
    // A subsystem whose Trigger port has Trigger type message leaves its parent's
    // order as a called one does, and runs from its own slot: decide() takes the
    // messages its Trigger face has received this step and hands the face to the
    // CALL DRIVER once per message, as R2026a measured (§F.BF4, with Schedule as
    // aperiodic partition off, the setting a fixed step allows):
    //   - Trigger time `on message available` (Message Triggered Subsystem): once
    //     per message, in the order they were sent -- two messages in a step run
    //     it twice in that step. Its face keeps every message that arrives.
    //   - `on sample time hit` (Message Polling Subsystem): at most once per hit,
    //     taking one message, and not at all on a hit without one. Its face keeps
    //     one message, the newest (measured: no backlog builds without a Queue).
    // Its outputs read their Initial output until the first message and hold
    // between runs; the Trigger port's own output is the message's value
    // (messagePayload), held between messages.
    static bool isMessageDriven(const ICoreSubsystemTreeNode* node);
    // The value of the message `face`'s run is processing, or processed last.
    // False, untouched, before the first message or on a shape it does not have.
    static bool messagePayload(const ICoreBlock* face, ICoreMatrix& value);

    class PassScope {
    public:
        PassScope(const ICoreSubsystemTreeNode* node, int pass);
        ~PassScope();
        PassScope(const PassScope&) = delete;
        PassScope& operator=(const PassScope&) = delete;
    private:
        class Impl;
        std::unique_ptr<Impl> impl;
    };

    // The sign rule, exposed for the regression suite: one step of an edge
    // detector over `previous` -> `current` given the transition before it.
    // `kind` is "rising", "falling" or "either".
    static bool edge(int beforePrevious, int previous, int current, bool havePrevious, bool haveBeforePrevious,
                     const char* kind);
};
};

ICoreSubsystemControls.h#

src/ICoreBlocks/ICoreModel/Block/GateBlock/ICoreSubsystemControls.h

ICoreSubsystemControls#

ICoreSubsystemControls.h:38 · class · 22 declaration(s)

THE BLOCKS THAT MAKE A SUBSYSTEM CONDITIONAL OR ITERATED (FEATURES_TO_ADD.md BF2.2, BF9.2): Simulink's Enable, Trigger and Reset port blocks and its For Iterator, While Iterator and For Each blocks.

class ICoreSubsystemControls {
public:
    // Action is the action subsystem's port block (BF8.2); If and SwitchCase are
    // the drivers that sit OUTSIDE the action subsystems they choose between.
    enum class Kind { None, Enable, Trigger, Reset, ForIterator, WhileIterator, ForEach, Action, If, SwitchCase, EventListener };

    // The registered block types.
    static const std::string ENABLE_PORT_TYPE;      // "Private/Subsystem_Components/Enable_Port"
    static const std::string TRIGGER_PORT_TYPE;     // ".../Trigger_Port"
    static const std::string RESET_PORT_TYPE;       // ".../Reset_Port"
    static const std::string FOR_ITERATOR_TYPE;     // ".../For_Iterator"
    static const std::string WHILE_ITERATOR_TYPE;   // ".../While_Iterator"
    static const std::string FOR_EACH_TYPE;         // ".../For_Each"
    static const std::string ACTION_PORT_TYPE;      // ".../Action_Port"
    static const std::string IF_TYPE;               // ".../If"
    static const std::string SWITCH_CASE_TYPE;      // ".../Switch_Case"
    static const std::string FUNCTION_CALL_SPLIT_TYPE;   // ".../Function_Call_Split"
    static const std::string FEEDBACK_LATCH_TYPE;        // ".../Function_Call_Feedback_Latch"
    static const std::string EVENT_LISTENER_TYPE;        // ".../Event_Listener"

    static Kind kindOfType(const std::string& fullType);
    static Kind kindOf(const ICoreBlock* block);
    static bool isControlPort(Kind kind);   // Enable, Trigger, Reset, Action
    static bool isDriver(Kind kind);        // If, Switch Case
    static bool isIterator(Kind kind);      // For, While, For Each

    // The text a control port's face shows: "Enable", "Trigger", "Reset",
    // "Action". "" for any other kind.
    static std::string faceLabel(Kind kind);

    // The role of a port on a Subsystem block's face: the kind of the control
    // port block it is paired with, or None for an ordinary input or output.
    static Kind roleOfFacePort(const ICorePort* facePort);

    // Whether `block` is a Trigger port whose Trigger type is function-call
    // (FEATURES_TO_ADD.md BF7.2): Simulink's Function-Call Subsystem, which
    // runs when its caller calls it, not on an edge.
    static bool isFunctionCallTrigger(const ICoreBlock* block);

    // Whether `block` is a Function-Call Split: a carrier, not a caller. One
    // call into it runs what each output reaches, output 1 first, depth first
    // (R2026a, FEATURES_TO_ADD.md BF7.1), and a second call does the same again.
    static bool isFunctionCallSplit(const ICoreBlock* block);

    // Whether `block` is a Function-Call Feedback Latch: a data pass-through that
    // carries a called subsystem's own output back to its input, from its
    // previous call (R2026a, measured: at two calls a step the second call reads
    // the first's value). The Latches feeding `face`'s data inputs directly, in
    // port order -- what the call driver and code export refresh before each call.
    static bool isFunctionCallFeedbackLatch(const ICoreBlock* block);
    static std::vector<const ICoreBlock*> feedbackLatchesInto(const ICoreBlock* face);

    // The Event type of the Event Listener directly inside `node` --
    // "Initialize", "Terminate", "Reset" or "Reinitialize" -- or "" when it
    // holds none (FEATURES_TO_ADD.md BF25). Such a subsystem is Simulink's
    // Initialize, Terminate, Reset or Reinitialize Function: it leaves the
    // step, Initialize runs once before the first output, Terminate once after
    // the last step, and the other two never run in a top model (R2026a).
    static std::string eventTypeOf(const ICoreSubsystemTreeNode* node);

    // Whether `block` is a Trigger port whose Trigger type is message
    // (FEATURES_TO_ADD.md BF4.6): Simulink's Message Triggered Subsystem, whose
    // run executes once per message that arrives on its face, or -- with Trigger
    // time `on sample time hit` (pollsMessages) -- its Message Polling Subsystem,
    // which takes at most one message per hit and does not run without one.
    static bool isMessageTrigger(const ICoreBlock* block);
    static bool pollsMessages(const ICoreBlock* block);

    // Whether a subsystem face port may carry a signal of `typeId`. A boundary
    // port carries any known type (ICorePort::acceptsType), except at a
    // control port: a function-call Trigger's face takes a function call and
    // nothing else, a message Trigger's face a message and nothing else, and
    // every other control face takes neither (R2026a: FcnCallConnMustBeSFcnOrCoreBlk,
    // MsgModePropError). No control face takes a complex number, which has no
    // real value to read (FEATURES_TO_ADD.md BF23.1). True for any other port.
    static bool controlFaceAccepts(const ICorePort* facePort, const std::string& typeId);

    // The port and iterator blocks directly inside `node` (not deeper: a nested
    // subsystem's controls are its own), in the node's order. A driver is not a
    // control of the level it sits in.
    static std::vector<ICoreBlock*> controlsOf(const ICoreSubsystemTreeNode* node);

    // Whether `node` is conditionally executed or iterated: it holds at least one
    // of the six. Such a subsystem keeps its run, as Simulink treats every
    // conditionally executed subsystem as nonvirtual.
    static bool controlsItsRun(const ICoreSubsystemTreeNode* node);

    // The selected value of a choice config ("A%~%B~~B" gives "B"), or the raw
    // value of any other config; "" when `block` has no config `name`.
    static std::string configValue(const ICoreBlock* block, const std::string& name);

    // What stops a run, and an export, that holds any of these blocks, one line
    // per problem, naming the block: one sits at the top level; a subsystem
    // holds two of one port kind or two iterators; an iterator's ports or
    // counts disagree with its configuration; a block inside a triggered
    // subsystem with its own rate or continuous states (InvBlkInTrigSubsys); a
    // For Each whose inputs do not partition, or that holds a block whose state
    // cannot be copied per slice (ICoreBlockSolverEnvironment::copiesStatePerSlice).
    static std::vector<std::string> refusals(const std::vector<ICoreBlock*>& blocks);

    // What code export refuses outright among these blocks, or "". Nothing since
    // FEATURES_TO_ADD.md BF9.8: For and While (BF9.5) and For Each (BF9.8)
    // export, and ICoreConditionalExport::refusal says per target what it cannot
    // carry. Kept as the one place a construct export cannot carry at all would
    // be named.
    static std::string exportRefusal(const std::vector<ICoreBlock*>& blocks);

    // ---- For Each: slicing the inputs, joining the outputs (FEATURES_TO_ADD.md BF9.4) ----
    //
    // A For Each subsystem's numbered inputs are partitioned per its For Each
    // block's lists, one entry per input or one for all, as R2026a measured them
    // (§F.BF9): a partitioned input of length L along its dimension gives
    // (L + offset) / (width + offset) slices, slice s taking `width` elements
    // from s * (width + offset), so a negative offset overlaps them; an input
    // that is not partitioned reaches every slice whole. Each output joins its
    // slices along its concatenation dimension.
    //
    // ICore's 1-D vector is a 1 x n row (a Constant [1 2 3]). Partitioned along
    // dimension 1 it is sliced along its length, as R2026a slices a 1-D vector,
    // and when every partitioned input is one, an output whose slices are rows
    // joins along dimension 1 into a longer row, as R2026a's 1-D result does.
    //
    // The face port's own subsystem decides; false, with nothing written, for a
    // port on any other subsystem, and for an input that is not partitioned.
    static bool forEachSliceShape(const ICorePort* faceInput, std::size_t& rows, std::size_t& cols);
    static bool forEachSlice(const ICorePort* faceInput, const ICoreMatrix& whole, std::size_t slice, ICoreMatrix& out);
    // From one slice of a partitioned face input to the next: the step along its
    // rows and along its columns, one of them 0 (code export indexes a slice by
    // the pass, BF9.8). False, untouched, as forEachSliceShape.
    static bool forEachSliceStep(const ICorePort* faceInput, std::size_t& rowStep, std::size_t& colStep);
    // How many slices `node` runs this step, read off its face inputs as they
    // stand; 0 when they do not partition (the refusals say why).
    static std::size_t forEachSliceCount(const ICoreSubsystemTreeNode* node);
    // A face output's shape for `slices` slices of rows x cols, and the join.
    static bool forEachJoinedShape(const ICorePort* faceOutput, std::size_t slices, std::size_t rows, std::size_t cols,
                                   std::size_t& outRows, std::size_t& outCols);
    static bool forEachJoin(const ICorePort* faceOutput, const std::vector<ICoreMatrix>& slices, ICoreMatrix& out);
};
};

ICoreBusSpec.h#

src/ICoreBlocks/ICoreModel/Block/Port/ICoreBusSpec.h

ICoreBusSpec#

ICoreBusSpec.h:26 · class · pImpl · nested Element · 12 declaration(s)

The shape of a bus signal: an ordered list of named elements, each with its own signal type and its own size.

class ICoreBusSpec {
public:

    // One element of the bus. Public data with no behaviour, like a registry
    // row: it is read by the selector block, the sizing loop, the build's size
    // check and four code generators, and none of them wants a getter.
    struct Element {
        // What the selector picks by, and the field name in the generated
        // struct. Unique within a spec -- addElement() refuses a duplicate.
        std::string name;

        // A signal-type registry id. Any id except ICoreBus itself.
        std::string typeId;

        // The element's own signal size. 1x1 for a String element.
        std::size_t rows = 1;
        std::size_t cols = 1;
    };

    ICoreBusSpec();
    ICoreBusSpec(const ICoreBusSpec& other);
    ICoreBusSpec& operator=(const ICoreBusSpec& other);
    ~ICoreBusSpec();

    // Appends an element. Refused (and logged) for an unknown type id, for
    // ICoreBus (no nesting in v1), for an empty name and for a name the spec
    // already carries. Returns whether the element was added.
    bool addElement(const std::string& name, const std::string& typeId,
                    std::size_t rows = 1, std::size_t cols = 1);

    void clear();

    [[nodiscard]] bool isEmpty() const;
    [[nodiscard]] std::size_t elementCount() const;

    // Element by position, or by the name a Bus_Selector was configured with.
    // nullptr when there is no such element -- that is the selector's error to
    // report, with the name it was looking for.
    [[nodiscard]] const Element* elementAt(std::size_t index) const;
    [[nodiscard]] const Element* elementNamed(const std::string& name) const;

    // Bus equality is the SIZE COMPARISON for a bus link (T2.6): two bus ports
    // fit together iff their specs are identical, element for element, in
    // order. Names are part of it -- a receiver selects by name, so a spec with
    // the same types under different names is a different spec.
    [[nodiscard]] bool operator==(const ICoreBusSpec& other) const;
    [[nodiscard]] bool operator!=(const ICoreBusSpec& other) const;

    // One line, for a diagnostic: `{ speed: ICoreDouble [1x1], ok: ICoreBool [1x1] }`.
    // A build error about two buses that do not match is unreadable without it.
    [[nodiscard]] std::string describe() const;

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreMessageDelivery.h#

src/ICoreBlocks/ICoreModel/Block/Port/ICoreMessageDelivery.h

ICoreMessageDelivery#

ICoreMessageDelivery.h:39 · class · 9 declaration(s)

When a message moves, in a live run.

class ICoreMessageDelivery {
public:
    // Before a committed solve, at time `t`, reads its inputs.
    static void deliverInto(const ICoreBlock* block, double t);
    // After a committed solve wrote its outputs, at the solve's time `t`.
    static void postFrom(const ICoreBlock* block, double t);

    // ---- What a block calls from its own solve ----
    // Both act only inside the block's COMMITTED solve (between deliverInto and
    // postFrom): compute_h also runs in the initial-output phase and in joint
    // sweeps, where a message must be neither taken nor held.
    // This solve sends nothing on `outputPort`.
    static void hold(const ICorePort* outputPort);
    // This solve sends `payload` on `outputPort`, after any it already sent:
    // a solve that calls send() on a port posts exactly what it sent there, in
    // order, and not the value it wrote -- the way one solve sends several
    // messages (a Message Merge forwarding two in one step). The payload is
    // sized like the port's value.
    static void send(const ICorePort* outputPort, const ICoreMatrix& payload);
    // The next message in `inputPort`'s store, by its policy; false when empty.
    static bool take(const ICorePort* inputPort, ICoreMatrix& payload, double& sendTime);
    static std::size_t waiting(const ICorePort* inputPort);
    // `inputPort`'s store policy (ICoreSignalValue::setMessageStore).
    static void setStore(const ICorePort* inputPort, std::size_t capacity, bool overwriteOldest, bool lastInFirstOut);

    // A run starts: `port` carries no message and no hold.
    static void beginRun(const ICorePort* port);

    // How deep a RELAY's store is: a sender's own carrier, a subsystem face and a
    // gate keep every message that passes until the next hop takes it, oldest
    // first, so a boundary never drops one. Only a receiver's policy decides
    // what is kept (FEATURES_TO_ADD.md BF4.6).
    static void makeRelayStore(ICoreSignalValue* store);
};
};

ICoreMessageTraffic.h#

src/ICoreBlocks/ICoreModel/Block/Port/ICoreMessageTraffic.h

ICoreMessageTraffic#

ICoreMessageTraffic.h:30 · class · nested Event · 9 declaration(s)

WHAT MESSAGES A RUN SENT, AND WHERE THEY WENT: the message traffic a sequence viewer draws as lifelines and arrows (FEATURES_TO_ADD.md BF4.7).

class ICoreMessageTraffic {
public:
    enum class Kind { Sent, Taken, Dropped };

    struct Event {
        Kind kind = Kind::Sent;
        double time = 0.0;          // when it happened
        double sendTime = 0.0;      // when the message was sent (Taken, Dropped)
        std::string sender;         // the sending block's path ("Home/Send")
        std::string reader;         // the reading block's path, "" when the line goes nowhere
        std::vector<double> value;  // the payload, row-major
        std::size_t rows = 0;
        std::size_t cols = 0;
    };

    static void beginRun();
    static void sent(const ICorePort* senderOutput, const ICoreMatrix& payload, double time);
    static void taken(const ICorePort* readerInput, const ICoreMatrix& payload, double sendTime, double time);
    static void dropped(const ICorePort* readerInput, std::size_t count, double time);

    static std::vector<Event> events();
    // What a Sequence Viewer at the level `levelPath` shows (Simulink: a viewer shows the
    // messages of the system it sits in and the systems below it): the events whose sender
    // or reader lies under `levelPath` -- every event for an empty one -- at most the last
    // `history` of them.
    static std::vector<Event> eventsWithin(const std::string& levelPath, std::size_t history);
    static std::size_t capacity();

    // What opens a viewer of the log; installed by the application's shell, so
    // a command can ask for one without naming the UI. nullptr until then.
    using ViewerOpener = void (*)();
    static void setViewerOpener(ViewerOpener opener);
    static bool openViewer();   // false when no viewer is installed

    // The block that finally reads what `senderOutput` sends, through every
    // subsystem boundary: a Receive's block, or the message-triggered subsystem
    // whose Trigger face reads it. nullptr when the line goes nowhere.
    static const ICoreBlock* readerOf(const ICorePort* senderOutput);
};
};

ICorePort.h#

src/ICoreBlocks/ICoreModel/Block/Port/ICorePort.h

ICorePort#

ICorePort.h:14 · class · pImpl · 48 declaration(s)

class ICorePort {
public:

    // preferredPortFacing is not guaranteed. ICorePortsPosAligner will determine if possible! Default alignment follow rules sat in ICorePortsPosAligner
    explicit ICorePort(ICoreBlock* parentBlock = nullptr, const std::string &type = "ICoreDouble",
            const std::string &initialPortDescription = "", const bool isOutputPort = false, const std::string& preferredFacing = "West");

    void setParentItem(ICoreBlockViewFrame* blockFrame) const;

    void setWirelessPortPairedTo(ICorePort* pairingPort);
    void clearWirelessPairing();
    ICorePort* getWirelessPortPairedTo() const;

    std::string getDescriptionLabelText() const;
    void setDescriptionLabelText(const std::string& newDescText) const;

    // Linearization analysis points, as R2026a stores them on an OUTPUT port: three
    // independent flags (its hidden LinearAnalysisInput, LinearAnalysisOutput and
    // LinearAnalysisOpenLoop parameters, measured, saved with the model). An input
    // perturbation, an output measurement and a loop opening at the signal this port
    // sources; ICoreJointIntegration::linearizationPoints reads them. Refused (false)
    // on an input port, which keeps all three off.
    bool setLinearizationFlags(bool input, bool output, bool openLoop);
    [[nodiscard]] bool isLinearizationInput() const;
    [[nodiscard]] bool isLinearizationOutput() const;
    [[nodiscard]] bool isLinearizationOpenLoop() const;

    [[nodiscard]] std::string capturePortState() const;

    void assignClonedProperties(const ICorePort* originalPort) const;

    std::string getName();

    [[nodiscard]] bool isOutputPort() const;
    [[nodiscard]] int getSerializationNumber() const;

    ICoreBlock* getParentBlock() const;

    [[nodiscard]] std::string getType() const;

    // --- Signal type -------------------------------------------------------
    // The type is a registry id (ICoreSignalType), and these four are the whole
    // of what the rest of the tree may do with it (design rows D4, T2.1-T2.3).
    //
    // Re-typing is for a port whose type is chosen by the block's CONFIG --
    // Constant's "Output data type", Data_Type_Conversion, a subsystem gate
    // mirroring what it is connected to. It re-types the link this port sources,
    // drops any downstream head that refuses the new type (that branch is left
    // dangling, exactly as a drop on empty canvas leaves one), and is refused
    // while a simulation is live. An unknown id is refused and logged; the port
    // keeps the type it had.
    void setType(const std::string& newTypeId);

    // The ids this port will accept from a link. Empty (the default) means "my
    // own type, and nothing else" -- so it follows setType() without a second
    // call, which is why the default is not stored as a one-element vector. The
    // set is the block's to declare in its constructor, like its port list; it
    // is not serialized, and a `.iproj` never carries one.
    // What a subsystem boundary does when a typed link lands on it: the gate
    // port takes the crossing type, and setType() carries it to the face port it
    // is paired with, so the same signal draws in the same style at both levels
    // and the outer link is re-typed with it (T7.13). A no-op on every port that
    // is not half of a wireless pair, which is every port outside a gate.
    void adoptCrossingSignalType(const std::string& crossingTypeId);

    void setAcceptedTypes(const std::vector<std::string>& acceptedTypeIds);
    [[nodiscard]] std::vector<std::string> getAcceptedTypes() const;
    [[nodiscard]] bool acceptsType(const std::string& typeId) const;

    // Whether this port's type is settled later than its connections are made:
    // its block registered ICoreBlockFactory::registerPortTypesSettleLater for
    // this side, or it is a subsystem boundary whose source on the other level
    // is itself settled later (BF1.11).
    [[nodiscard]] bool isTypeSettledLater() const;

    // The connection-time check: acceptsType(typeId), unless either end's type
    // is settled later -- then the connection is made, and the build's own type
    // check (verifyLinksSignalTypeAndSize) is the one that answers. `source` may
    // be null when nothing drives the link yet.
    [[nodiscard]] bool acceptsConnectionFrom(const ICorePort* source, const std::string& typeId) const;

    void enforceSettingPortSerializationNumber(const int& newSerializationNumber);

    void overwriteSignalValue(const ICoreMatrix& newVal) const;

    [[nodiscard]] ICorePortView* getPortUI() const;

    // The two things the model bridge needed from the port's view, said by the
    // port instead. Same reasoning as the appearance block in ICoreBlock.h:
    // reaching through getPortUI() made ICorePortSolverEnvironment and
    // ICoreModelVerification -- neither of which draws anything -- compile
    // against the editor's widget stack to resize a label and read a position.
    //
    // ICorePoint rather than the view's own point type, so this header stays
    // free of raw toolkit names; the forwarding site converts, and the one call
    // site already assigned the result to an ICorePoint anyway.
    void setSignalSizeLabel(int numOfRows, int numOfColumns) const;
    [[nodiscard]] ICorePoint getCanvasBaseCoordinates() const;

    std::string getPath() const;

    void setIsConnected(bool newIsConnected);
    void setConnectionLinkBranch(ICoreLinkBranch* newLinkBranch);
    [[nodiscard]] bool getIsConnected() const;
    [[nodiscard]] ICoreLinkBranch* getConnectionLinkBranch() const;
    ICorePort* getConnectionSource() const;
    bool isConnectedToAnyInputPort() const;
    ICorePortSolverEnvironment* getSolverEnvironment() const;

    ICorePort* isConnectedToValidOutputPort() const;

    //const std::string& getConnectionThread() const;
    //ICorePort* getConnectedToPort() const;

    ICoreMatrix* getSignal() const;

    // Layer 4: canonical "remove me from the live model graph" — sever the cross-references a port
    // can dangle (link-branch connection + wireless gate pairing). Idempotent. Called by
    // collectGarbage_Port. NOTE: ports-vector removal + gate-mirror teardown stay in
    // ICoreBlock::givePortOwnership (single-shot, not idempotent), so detachFromModel does NOT route
    // through it.
    void detachFromModel();

    // --- Clear connection ---
    void clearConnection();
    //void removeOtherSidePortConnectionProperty(ICorePort* otherSidePort);
    void removePortConnection(ICorePort* portToRemove, ICoreLink* connectionLink);

    ICoreSubsystemTreeNode* getGrandParentTreeNode() const;
    void printConnectionDetails() const;

    void resetToInitialState(ICoreBlock* parentBlock, const std::string &type, const std::string &initialPortDescription,
                const bool isOutputPort, const std::string& preferredFacing);
    void kill();
    void setAlive();
    bool isAlive() const;

    ~ICorePort();

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreSignalType.h#

src/ICoreBlocks/ICoreModel/Block/Port/ICoreSignalType.h

ICoreSignalType#

ICoreSignalType.h:27 · class · nested Row, FixedPoint · 7 declaration(s)

The signal-type registry -- the one list of port type ids in the tree.

class ICoreSignalType {
public:

    // The family a type belongs to. The kind -- not the id -- is what selects
    // the value carrier (D2), the write-time rule (D5), the wire stroke (D6)
    // and the verification stimulus, so a new id of an existing kind costs
    // those four nothing.
    enum class Kind {
        Floating,
        Integer,
        Boolean,
        String,
        Bus,
        // A function call (FEATURES_TO_ADD.md BF7.2): Simulink's `fcn_call`. It
        // carries no value -- logging one, or wiring one where data is read,
        // is refused -- and says only that its source CALLS what it reaches.
        // The port's matrix holds how many calls the source made this step,
        // one element per call line of a wide call, so a caller with an
        // iteration count of 3 writes [3]; nothing else may read it as data.
        FunctionCall,
        // A message (FEATURES_TO_ADD.md BF4.2): a value and the time it was sent,
        // CONSUMED where it is read, where every kind above holds. The port's
        // matrix is sized like the payload, as a value port's is; the messages
        // themselves wait in the side carrier's store (ICoreSignalValue), whose
        // policy is Simulink's. One message line feeds one receiver, and a
        // message line never connects to a signal port.
        Message,
        // An enumeration (FEATURES_TO_ADD.md BF19.3): Simulink's
        // Simulink.IntEnumType on a wire. The port's matrix holds each
        // element's UNDERLYING int32 value; WHICH type it is -- a project type
        // in ICoreEnumRegistry -- rides on the side carrier
        // (ICoreSignalValue::setEnumTypeName), because the registry here is a
        // closed list and the project's types are not. Not numeric: R2026a
        // refuses arithmetic on one, and comparison with a number.
        Enumeration,
        // A fixed-point number (FEATURES_TO_ADD.md BF14.2): Simulink's
        // fixdt(s, w, f) with binary-point scaling, a w-bit integer -- signed
        // or not -- times 2^-f. The port's matrix holds the REAL-WORLD value,
        // which a double holds exactly for w <= 53; WHICH fixdt rides on the
        // side carrier (ICoreSignalValue::setFixedPointType), as an
        // enumeration's type does, because the family is parameterised and
        // this registry is a closed list. Numeric. The write-time rule is D5's
        // for an integer, on the 2^-f grid: truncate toward zero, then wrap to
        // w bits; fixdt(s, w, 0) is exactly the w-bit integer.
        Fixed,
        // A complex number (FEATURES_TO_ADD.md BF23.1): Simulink's complex
        // signal, a real and an imaginary part per element. INTERLEAVED in the
        // port's matrix: an [r, c] complex signal is an r x 2c matrix whose
        // column 2k is element k's real part and column 2k+1 its imaginary
        // part -- the layout of Simulink Coder's creal_T arrays and of MATLAB's
        // interleaved complex API, so every target stores it as the doubles it
        // already stores. Numeric, but NOT in the numeric accepted set
        // (ICoreBlock::numericSignalTypeIds): a block that reads numbers would
        // read a pair as two of them, so only a block that declares the kind
        // takes one.
        Complex
    };

    // One row of the registry. Public data with no behaviour: a row is read by
    // the port, the solver, four maps in three other layers and one guard, and
    // none of them wants a getter.
    struct Row {
        // The wire identity. Permanent once a file carries it.
        std::string id;

        // What the canvas draws beside the port. Four characters at most --
        // ICorePortViewSignalTypeLabel has no room for a fifth.
        std::string badge;

        Kind kind = Kind::Floating;

        // Width in bits, and whether the top bit is a sign. Read by the
        // integer wrap (D5) and by the HDL width. Zero for String, Bus and
        // FunctionCall, which have no width; a Message is its double payload's.
        int bits = 0;
        bool isSigned = false;

        // What "nothing" looks like before a target language's own map
        // overrides it at export time. Language-neutral on purpose.
        std::string zeroLiteral;
    };

    // The type every untyped port is created as, every `addPort` without a type
    // resolves to, and every unknown id falls back to.
    static const char* const DEFAULT_ID;

    // The one id of the Fixed kind, whatever fixdt a port carries.
    static const char* const FIXED_ID;

    // The one id of the Complex kind (FEATURES_TO_ADD.md BF23.1).
    static const char* const COMPLEX_ID;

    // One fixdt(s, w, f): signed or not, word length w, fraction length f (a
    // negative f scales by a power of two above one). A port typed FIXED_ID
    // and naming none is fixdt(1, 16, 0), Simulink's default word.
    struct FixedPoint {
        bool isSigned = true;
        int wordLength = 16;
        int fractionLength = 0;
    };
    // The longest word this tree carries: a double holds every w-bit integer
    // exactly up to 53 bits, and every value of the family is one of those
    // times a power of two.
    static constexpr int FIXED_MAX_WORD = 53;
    static constexpr int FIXED_MAX_FRACTION = 64;
    // Simulink's spelling, `fixdt(1,16,4)`, spaces allowed, into `out`. False,
    // with the reason in `why`, for anything else -- `fixdt(1,16)`, whose
    // scaling Simulink leaves to best precision, a slope and bias, a word
    // longer than FIXED_MAX_WORD or a fraction outside +/-FIXED_MAX_FRACTION.
    static bool parseFixedPoint(const std::string& text, FixedPoint& out, std::string* why = nullptr);
    // The canonical spelling, `fixdt(1,16,4)`: no spaces, as a choice, a file
    // and the Simulink bridge carry it.
    static std::string spellFixedPoint(const FixedPoint& type);

    // The whole table, in the order the canvas and the Block Wizard show it.
    static const std::vector<Row>& all();

    // Whether an id names a row. This is the predicate that makes a type exist:
    // an id that is not here is refused by the port constructor, by the
    // recipe's `addPort` and by the Block Wizard.
    static bool isKnown(const std::string& id);

    // The row an id names, or nullptr. The three fields that have no accessor
    // of their own -- bits, isSigned, zeroLiteral -- are reached through this.
    static const Row* find(const std::string& id);

    // Total lookups: an id with no row answers as DEFAULT_ID does, because
    // every path that can produce an unknown id (a stale `.iproj`, a hand-typed
    // recipe) already falls back to `ICoreDouble` rather than refusing to open
    // the file. Call isKnown() first where the difference matters -- the
    // diagnostic is the caller's to log, not the table's.
    static Kind kindOf(const std::string& id);
    static std::string badgeOf(const std::string& id);

};
};

ICoreSignalValue.h#

src/ICoreBlocks/ICoreModel/Block/Port/ICoreSignalValue.h

How long a string signal is, everywhere.

ONE constant, because a live run and exported C have to truncate at the same place or a verified export is comparing a 40-character string against a 32-character one and calling it a mismatch. C declares char [N], PLC ST declares STRING[N], and this is that N. It counts BYTES of UTF-8, not code points, so the exported buffer and the live value are the same size.

ICoreSignalValue#

ICoreSignalValue.h:36 · class · pImpl · 27 declaration(s)

The value a port carries when a matrix cannot carry it.

class ICoreSignalValue {
public:

    ICoreSignalValue();
    ~ICoreSignalValue();

    // Back to "no string, no bus". Called when a port is recycled onto another
    // block, which may be of any type.
    void clear();

    // ---- String -----------------------------------------------------------
    //
    // UTF-8, truncated to ICORE_STRING_CAPACITY bytes on the way in, so the
    // value in memory is always one an exported buffer could hold. Truncation
    // is silent by design: it happens per sample inside a running solver, and a
    // diagnostic per sample would be the only thing in the log.
    [[nodiscard]] const std::string& getString() const;
    void setString(const std::string& text);

    // ---- Bus --------------------------------------------------------------
    //
    // The spec says what the bus IS; the element values are what it carries
    // this step. Setting the spec RESIZES the element values to match and
    // zeroes them: a bus whose shape changed is not carrying its old contents.
    [[nodiscard]] const ICoreBusSpec& getBusSpec() const;
    void setBusSpec(const ICoreBusSpec& spec);

    // The value of one element, by position in the spec. nullptr when the index
    // is past the end. A String element's matrix is a 1x1 placeholder, exactly
    // as a String port's is -- read getBusElementString() for those.
    [[nodiscard]] ICoreMatrix* getBusElementValue(std::size_t index) const;
    [[nodiscard]] const std::string& getBusElementString(std::size_t index) const;
    void setBusElementString(std::size_t index, const std::string& text);

    // Writes one element's value for this step, IN PLACE. Refused (false, and
    // nothing written) when the index is past the end or the value is not the
    // spec's rows x cols: a bus's shape is fixed for the run once the build has
    // sized it (FEATURES_TO_ADD.md BF1.4), and an element that quietly took
    // another shape would disagree with its spec, with every consumer that
    // sized against it, and with the record the exported code declares. The
    // caller names the block when it reports the refusal.
    bool setBusElementValue(std::size_t index, const ICoreMatrix& value);

    // ---- The shape a consumer requires (BF1.4) ----------------------------
    //
    // An input that reads a bus of a KNOWN shape -- a learner's Predict block
    // reads the bus its own learner config implies -- declares it here, and
    // the build compares what arrives with it after sizing and refuses a
    // mismatch by name. Without it the build compares a head with the copy
    // sizing gave it of its own tail, which always agrees.
    //
    // Declaring a shape also takes it, zeroed, so an unconnected input carries
    // a bus of the right shape, as an unconnected numeric input carries zeros
    // of its size. It belongs to the PORT, not to the value on it:
    // cloneValueFrom() never copies it, and clear() drops it.
    void requireBusSpec(const ICoreBusSpec& spec);
    void clearRequiredBusSpec();
    // nullptr when nothing is required.
    [[nodiscard]] const ICoreBusSpec* getRequiredBusSpec() const;

    // ---- Enumeration (FEATURES_TO_ADD.md BF19.3) ---------------------------
    //
    // The project type (ICoreEnumRegistry) an Enumeration port's values are
    // members of. The values themselves are the port matrix's underlying int32
    // numbers. A block that outputs an enumeration names its type here; sizing
    // carries it to every head downstream, as it carries a bus's spec, and
    // cloneValueFrom() copies it. Empty means no type was named.
    [[nodiscard]] const std::string& getEnumTypeName() const;
    void setEnumTypeName(const std::string& typeName);

    // ---- Fixed point (FEATURES_TO_ADD.md BF14.2) ----------------------------
    //
    // Which fixdt(s, w, f) a Fixed port's values are, in Simulink's canonical
    // spelling (`fixdt(1,16,4)`). The values themselves are the port matrix's
    // real-world numbers. Named by the block that outputs one, carried to every
    // head by sizing and copied by cloneValueFrom(), as an enumeration's type
    // is. Empty means none was named. setFixedPointType() keeps a spelling only
    // ICoreSignalType::parseFixedPoint() reads, and stores it canonically.
    [[nodiscard]] const std::string& getFixedPointType() const;
    void setFixedPointType(const std::string& spelling);
    // The type named, or fixdt(1, 16, 0) -- the family's default -- when none is.
    [[nodiscard]] bool fixedPoint(bool& isSigned, int& wordLength, int& fractionLength) const;

    // ---- Copying ----------------------------------------------------------
    //
    // What readInputPorts does for the matrix, done for the carrier: the
    // downstream port takes a COPY of the value, never a reference to the
    // upstream port's, because the upstream block will overwrite its own on the
    // next step.
    //
    // It runs for every connected input on every step, so a copy onto an
    // unchanged shape -- every step after the first -- is IN PLACE: no element
    // storage is freed or allocated (BF1.4; a learner bus reaches 65 580
    // numbers). Only a changed spec rebuilds the storage.
    void cloneValueFrom(const ICoreSignalValue& other);

    // ---- Message ----------------------------------------------------------
    //
    // A message is a value and the time it was sent, and it is CONSUMED when it
    // is read, where a value holds. A message port's matrix is sized like the
    // payload, as any value port's is; the messages themselves wait here, in
    // a STORE whose policy is Simulink's (measured on R2026a, FEATURES_TO_ADD.md
    // BF4.1): a Receive without an internal queue keeps ONE message and the
    // newest overwrites it, which is the default here; a Queue holds a capacity,
    // LIFO by default, FIFO or LIFO, and on overflow either drops the OLDEST
    // (overwrite on) or refuses the NEWEST and counts it (overwrite off).
    //
    // cloneValueFrom never copies messages: a copied message would be delivered
    // twice. They move, by moveMessagesInto, and only there.

    // The store's policy. A capacity below 1 is taken as 1.
    void setMessageStore(std::size_t capacity, bool overwriteOldest, bool lastInFirstOut);
    [[nodiscard]] std::size_t messageCapacity() const;

    // Adds a message under the store's policy. False when it was refused (the
    // store full with overwrite off), which also counts it as dropped.
    bool pushMessage(const ICoreMatrix& payload, double sendTime);

    // Takes the next message by the store's order (the newest for LIFO, the
    // oldest for FIFO). False, and nothing written, when the store is empty.
    bool popMessage(ICoreMatrix& payload, double& sendTime);

    [[nodiscard]] std::size_t messageCount() const;
    // Messages refused because the store was full with overwrite off, since the
    // last clear. R2026a warns about these (MessageSendPortBlocked); it never
    // errors.
    [[nodiscard]] std::size_t droppedMessageCount() const;

    // Delivery: every message waiting here moves into `receiver`, oldest first,
    // under the RECEIVER's policy, and this store is left empty. Answers how
    // many arrived (dropped ones not counted). Moving into itself does nothing.
    std::size_t moveMessagesInto(ICoreSignalValue& receiver);

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreDataStoreRegistry.h#

src/ICoreBlocks/ICoreModel/DataStores/ICoreDataStoreRegistry.h

ICoreDataStoreRegistry#

ICoreDataStoreRegistry.h:34 · class · nested Finding · 13 declaration(s)

Named data stores: Simulink's Data Store Memory, and the scope rule its Data Store Read and Data Store Write blocks resolve a name by.

class ICoreDataStoreRegistry {
public:
    // Declares the store `name`, owned by `owner` at the owner's level, starting
    // at `initialValue`. A second declaration from the same owner replaces its
    // first (its name or value may have changed). "" on success; otherwise the
    // refusal, and nothing is declared: an empty name, an owner with no level,
    // or another owner at the same level already declaring this name.
    static std::string declare(const ICoreBlock* owner, const std::string& name, const ICoreMatrix& initialValue);

    // Forgets whatever `owner` declared. Nothing to forget is not an error.
    static void withdraw(const ICoreBlock* owner);

    // Forgets every store.
    static void clear();

    // The owner of the store `name` as seen from `level`: the declaration at
    // `level` itself, else at its parent, and so on up to the root. nullptr when
    // no level on that path declares it.
    static const ICoreBlock* resolve(const ICoreSubsystemTreeNode* level, const std::string& name);

    // The same, from the level `accessor` sits in -- how a Read or a Write finds
    // its store.
    static const ICoreBlock* resolveFor(const ICoreBlock* accessor, const std::string& name);

    // Simulink's words for a name `accessor` cannot resolve.
    static std::string notFoundMessage(const std::string& name, const ICoreBlock* accessor);

    // The current value of the store `owner` declared; false when it declared none.
    static bool read(const ICoreBlock* owner, ICoreMatrix& value);

    // Replaces the current value of the store `owner` declared. False, with
    // nothing written, when it declared none or `value` is not the store's shape.
    static bool write(const ICoreBlock* owner, const ICoreMatrix& value);

    // Every store back to its initial value: the start of a run.
    static void resetValues();

    // How many stores are declared, over every level.
    static std::size_t storeCount();

    // What `owner` declared: the store's name and its initial value. False when
    // it declared none. Code export reads this to give each store a named
    // global seeded with that value (FEATURES_TO_ADD.md BF10.4).
    static bool declaration(const ICoreBlock* owner, std::string& name, ICoreMatrix& initialValue);

    // ---- The three per-store diagnostics (BF10.4), as R2026a reports them ----
    //
    //   ReadBeforeWrite  a read, in a step, of an element no block has written
    //                    yet in that step -- which includes every read of a
    //                    store nothing writes, and every step of a loop through
    //                    one store (Read -> ... -> Write). Default: none.
    //                    Simulink:DataStores:DSReadBeforeWrite.
    //   WriteAfterWrite  a write, in a step, to an element another block has
    //                    already written in that step. Writers to DISJOINT
    //                    elements are not one. Default: WARNING.
    //                    Simulink:DataStores:DSWriteAfterWrite.
    //   WriteAfterRead   a write, in a step, to an element already read in that
    //                    step. Default: none. Simulink:DataStores:DSWriteAfterRead.
    //
    // Each is reported ONCE per store and run -- R2026a's "occurrences ... at
    // other simulation time steps will be suppressed" -- and a new declaration
    // (a run's config load) starts the count again. A step is recognised by its
    // time: the first access at a time other than the last one begins a new one.
    //
    // The registry only FINDS them. The accessor that made the access reports
    // what comes back, as a warning or as the error that stops the run, because
    // the run and its log are the accessor's and not this registry's.
    enum class Severity { None, Warning, Error };
    enum class Check { ReadBeforeWrite, WriteAfterWrite, WriteAfterRead };
    struct Finding {
        Severity severity = Severity::None;   // None: nothing to report
        Check check = Check::ReadBeforeWrite;
        std::string id;                       // Simulink's message ID
        std::string message;                  // Simulink's words, with the paths
    };

    // The severity `owner`'s store reports `check` at. R2026a's defaults until set.
    static void setSeverity(const ICoreBlock* owner, Check check, Severity severity);
    static Severity severity(const ICoreBlock* owner, Check check);

    // Records that `accessor` read or wrote `owner`'s store at `time`, and returns
    // what that access makes reportable. `elements` are the linear (row-major)
    // indices the access touched; empty means the whole store. A store `owner`
    // did not declare is never a finding.
    static Finding noteRead(const ICoreBlock* owner, const ICoreBlock* accessor, double time,
                            const std::vector<std::size_t>& elements = {});
    static Finding noteWrite(const ICoreBlock* owner, const ICoreBlock* accessor, double time,
                             const std::vector<std::size_t>& elements = {});
};
};

ICoreEnumRegistry.h#

src/ICoreBlocks/ICoreModel/Enumerations/ICoreEnumRegistry.h

ICoreEnumRegistry#

ICoreEnumRegistry.h:27 · class · nested Member, Type · 5 declaration(s)

The project's user-defined enumerated types: Simulink's Simulink.IntEnumType, defined in the project as a recipe statement, defineEnum(BfMode, {Off, On, Fault}, [0 1 9], Off) which a project save w...

class ICoreEnumRegistry {
public:
    struct Member {
        std::string name;
        int value = 0;
    };
    struct Type {
        std::string name;
        std::vector<Member> members;
        std::string defaultMember;
    };

    // Defines (or replaces) the type `name`. `defaultMember` empty means the first
    // member. "" on success; otherwise R2026a's reason, and nothing changes.
    static std::string define(const std::string& name, const std::vector<std::string>& members,
                              const std::vector<double>& values, const std::string& defaultMember);

    // Forgets the type `name`; false when there is none.
    static bool remove(const std::string& name);

    // Forgets every type: a project is closed, wiped or about to be replayed.
    static void clear();

    // The type `name` (case-sensitive); false when none is defined.
    static bool find(const std::string& name, Type& type);

    // Every type, in the order they were first defined.
    static std::vector<Type> types();

    // The recipe statement that defines `type`, default member included.
    static std::string statementFor(const Type& type);
};
};

ICoreEditorHost.h#

src/ICoreBlocks/ICoreModel/Host/ICoreEditorHost.h

ICoreEditorHost#

ICoreEditorHost.h:40 · class · nested ConsoleResult · 23 declaration(s)

ICoreEditorHost -- what the simulation kernel needs FROM whatever is hosting it, expressed as something the kernel owns.

class ICoreEditorHost {
public:
    virtual ~ICoreEditorHost() = default;

    // Matches the levels the run-diagnosis panel already understood, so the
    // meaning of a level is unchanged by the move -- only who names it.
    enum class DiagnosticLevel {
        Log     = 0,
        Warning = 1,
        Error   = 2,
    };

    // Outcome of evaluating one console line. A plain value type so the kernel
    // is not handed a widget's result struct.
    struct ConsoleResult {
        bool ok = false;
        std::string output;
    };

    // --- run diagnostics ---------------------------------------------------
    virtual void diagnosticLogged(const std::string& message, DiagnosticLevel level);
    virtual void diagnosticsCleared();

    // --- user-facing notifications -----------------------------------------
    virtual void notifyFriendly(const std::string& title, const std::string& message);
    virtual void notifyWarning(const std::string& title, const std::string& message);
    virtual void notifyError(const std::string& title, const std::string& message);

    // --- simulator lifecycle -----------------------------------------------
    virtual void solverStarted();
    virtual void solverPaused();
    virtual void solverStopped(bool wasAborted);
    virtual void debugModeStarted();
    virtual void progressReset();

    // A run that ends BEFORE the solver starts -- the build failed, or the canvas has
    // nothing on it. NOT solverStopped: no solver ran, so there is no outcome to
    // announce, and whoever aborted has already said why (a "Nothing to Simulate"
    // notification, a build failure in the diagnostics). A host still has to hear it,
    // because a run's controls go inert the moment the build starts and this is the
    // only thing left that can put them back.
    virtual void solverStartupAborted();

    // --- model configuration ------------------------------------------------
    virtual void startTimeChanged();
    virtual void stopTimeChanged();

    // --- model build lifecycle ----------------------------------------------
    //
    // modelBuildFinished is raised for BOTH outcomes, from the one place the build
    // returns from. modelBuildFailed stays what it always was -- the failure-only
    // hook the port signal-type tags key off -- so a host that wants "the build is
    // over, whatever it did" has something to hang a busy indicator off, and one
    // that only cares about failure is unchanged.
    virtual void modelBuildStarted();
    virtual void modelBuildFinished(bool succeeded);
    virtual void modelBuildFailed();

    // --- navigation policy ---------------------------------------------------
    virtual void navigationAccessChanged();

    // The root-access flag was CHANGED, and the host is the thing that can make
    // that survive a restart.
    //
    // ICoreSubsystemTreeNodeRegistry owns the value -- getNavigationRootTreeNode()
    // has to answer without asking anybody -- but WHERE a user setting is stored
    // is a front-end concern, and the kernel may not name the preferences panel
    // to say so (MODULE_LAYERING R5g). So the registry keeps the value and
    // raises this; the host persists it.
    //
    // ⚠ Not the same hook as navigationAccessChanged() above, which is a
    // "refresh the open windows" notification with no argument and fires from
    // the console command. This one carries the new value and exists so the
    // setting is written down. A host may well want both.
    virtual void navigationRootAccessAllowedChanged(bool allowed);

    // --- the one REQUEST rather than notification ----------------------------
    //
    // Everything above tells the host something happened. This asks it to do
    // something and hands back the answer, because the console grammar the
    // script runner needs lives in the panel that implements it. The default
    // returns a failed result rather than pretending to have run the line: a
    // host with no console genuinely cannot evaluate one, and silently
    // reporting success would make a script look like it had worked.
    virtual ConsoleResult evaluateConsoleLine(const std::string& line);

    // The console just declared or changed a variable in the global space.
    // The editor mirrors its table view and autosaves the project when the
    // user has autosave on; a headless process has nothing to do.
    virtual void consoleVariablesChanged();

    // The host in force. Never null -- with none registered this is a shared
    // do-nothing instance, which is exactly right for a headless process.
    static ICoreEditorHost& instance();

    // Registers the host. Pass nullptr to go back to the do-nothing default,
    // which is what a teardown should do rather than leave a dangling host
    // behind for late kernel activity to call into.
    static void setInstance(ICoreEditorHost* host);
};
};

ICoreLink.h#

src/ICoreBlocks/ICoreModel/Link/ICoreLink.h

Layer 4: canonical "remove me from the live model graph" — detach from parent container + canvas/selection. Idempotent. Called by collectGarbage_Link so collection is self-sufficient.

ICoreLink.h:11 · class · pImpl · 39 declaration(s)

class ICoreLink {
public:
    explicit ICoreLink(ICoreSubsystemTreeNode* parent, std::string type);

    std::string generateUniqueName(const std::string& prefix) const;

    ICoreLinkBranch* createNewBranch();
    void giveBranchOwnershipUp(ICoreLinkBranch* branchToGiveUp, bool forceRootBranchRemoval);
    void acquireBranchOwnership(ICoreLinkBranch* branchToAcquire);
    void deleteBranch(ICoreLinkBranch* branchToDelete);

    void clearAllBranches_IncludingRoot();

    void addToCanvas(ICoreCanvas* canvas);
    void freeFromCanvas();

    // Layer 4: canonical "remove me from the live model graph" — detach from parent container +
    // canvas/selection. Idempotent. Called by collectGarbage_Link so collection is self-sufficient.
    void detachFromModel();

    std::string parseRootBranchState() const;

    static std::string parseNoneRootBranchState(const ICoreLinkBranch* branchToCaptureState);
    ICoreCanvasObjectState* getState() const;
    void updateToState(ICoreCanvasObjectState* desiredState);
    void updateRootBranchToState(const ICoreCanvasObjectState* fullLinkDesiredState) const;
    void updateNoneRootBranchesToState(const ICoreCanvasObjectState* fullLinkDesiredState);
    static std::vector<std::string> splitByDollarSign(const std::string& input);
    static std::vector<ICorePoint> parsePoints(const std::string& str);

    void ensureAllBranchesSplitFromLinkRoot() const;

    void setParent(ICoreSubsystemTreeNode* newParent);

    void setName(const std::string& newName);

    ICoreSubsystemTreeNode* getParent() const;
    std::string getName();
    std::string getType();

    // A link's type is its TAIL (source) port's type, and nothing else
    // (design row D4). Called when a tail port is connected and when a
    // connected tail port is re-typed; a link with no tail keeps whatever it was
    // created with, which is what its dangling styling is drawn from.
    void setType(const std::string& newTypeId);
    std::string getPath() const;

    ICoreCanvas* getDrawnToCanvas() const;
    std::string getClassID() const;

    ICoreLinkBranch* getRootBranch() const;
    std::vector<ICoreLinkBranch*> getAllBranches() const;

    void setTrashOrder(const int newTrashOrder);
    int getTrashOrder() const;
    void increaseTrashOrder();
    void decreaseTrashOrder();

    void resetToInitialState(ICoreSubsystemTreeNode* parent, std::string type);
    void kill();
    void setAlive();
    bool isAlive() const;

    ~ICoreLink();

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreLinkBranch.h#

src/ICoreBlocks/ICoreModel/Link/ICoreLinkBranch.h

Corners are MODEL coordinates. There used to be two overloads here -- one taking the toolkit's point vector and one taking ICorePoint -- because a std::vector does not convert between value types even when its elements do. Now that the branch stores ICorePoint, the two collapsed into this one; it does NOT touch the sign of y (these coordinates are already in the +y-down sense, same contract as ICoreRecipeStudioBridge's).

Taken BY VALUE because optimize_unusedPath rewrites the path in place.

ICoreLinkBranch#

ICoreLinkBranch.h:14 · class · pImpl · 82 declaration(s)

class ICoreLinkBranch {
public:
    explicit ICoreLinkBranch(ICoreLink* parentLink = nullptr);

    // Corners are MODEL coordinates. There used to be two overloads here -- one
    // taking the toolkit's point vector and one taking ICorePoint -- because a
    // std::vector does not convert between value types even when its elements
    // do. Now that the branch stores ICorePoint, the two collapsed into this
    // one; it does NOT touch the sign of y (these coordinates are already in
    // the +y-down sense, same contract as ICoreRecipeStudioBridge's).
    //
    // Taken BY VALUE because optimize_unusedPath rewrites the path in place.
    void setBranchCorners(std::vector<ICorePoint> newBranchCorners, bool allowUpdateUI);

    void setBranchCorners_RelativeToCanvasOriginAnchor(const std::vector<ICorePoint> &newBranchCorners, bool allowUpdateUI);
    void reRunPathPlannerOverUnusedPath(std::vector<ICorePoint>& pathToRecreate);
    void updateCornerCoordinates(const int& index, const ICorePoint& newCorner);
    void translateCorner(const int& index, const double& dx, double& dy);
    void translateBranch(ICorePoint delta);
    void offsetPosition(ICorePoint delta);
    void ungrabMouse() const;
    void assignBranchConnection(ICorePort* tailPortToAssign, ICoreLinkBranch* branchSplittingFrom,
                                ICorePort* headPortToAssign, bool moveBranchOwnership);

    // Add new branch segments. Call from here
    ICoreLinkBranchSegment* addSegmentAtStart();
    ICoreLinkBranchSegment* addSegmentAtEnd();
    ICoreLinkBranchSegment* addSegmentAtIndex(int index);

    // ====== These three don't add the new segments to the corners list (Raw). Call them from addSegmentAt...
    ICoreLinkBranchSegment* createNewSegment_insertAtBranchStart(ICorePoint startCorner, ICorePoint endCorner);
    ICoreLinkBranchSegment* createNewSegment_insertAtBranchEnd(ICorePoint startCorner, ICorePoint endCorner);
    ICoreLinkBranchSegment* createNewSegmentAtIndex(int index);

    static double computeDirectionalCost(
        const ICorePoint& point,
        const ICorePoint& closestOnSegment,
        const ICorePoint& segmentStart,
        const ICorePoint& segmentEnd);

    static ICorePoint computeVector(const ICorePoint& from, const ICorePoint& to);

    void addCornerAtEnd(ICorePoint newCorner);

    void clearAllSegments();

    // Drops the model-coordinate corner list. Pairs with clearAllSegments() and
    // is called from the same place: ICoreStudioGarbageCollection::
    // collectGarbage_LinkBranch, which resetToInitialState() has always claimed
    // clears it (Linux backend plan §L118.5, §L121.5). Until 2026-08-29 nothing
    // did, and a pooled branch carried its old corners until it was reused.
    // Separate from setBranchCorners(), which cannot express "no corners" -- it
    // indexes [0] and [size-1] to derive the tail/head coordinates.
    void clearAllCorners();
    // void resetAllBranchSegment() const;
    // void adjustNumberOfSegment(const int& newNumberOfSegment);

    void assignClonedProperties(const ICoreLinkBranch* originalBranch);

    void setTailPort(ICorePort* newTailPort);
    void setHeadPort(ICorePort* newHeadPort);
    ICorePort* getTailPort() const;
    ICorePort* getHeadPort() const;

    ICoreLinkBranchTail* getBranchTail() const;
    ICoreLinkBranchHead* getBranchHead() const;

    void addToCanvas(ICoreCanvas* canvas);
    void freeFromCanvas();

    ICorePoint getClosestPointOnBranch(const ICorePoint& PointToCheckTo) const;
    ICoreLinkBranchSegment* getClosestSegmentToPoint(const ICorePoint& targetPoint) const;

    void updateUI();

    void updateHeadAndTailPosOrientation() const;

    void setConnectedStyle() const;
    void setUnconnectedStyle() const;

    void optimize_unusedPath(std::vector<ICorePoint>& newBranchCorners);
    void optimize_usedPath();

    static int countCorners(const std::vector<ICorePoint>& pathToCount);
    bool doesPointLayOnBranch(const ICorePoint& pointToCheck) const;
    void minimizeCorners(std::vector<ICorePoint> &originalPath) const;
    static ICorePoint tryUpdatingPerpendicularCorner(const ICorePoint& previousCorner, const ICorePoint& thisCorner, const ICorePoint& nextCorner);
    std::vector<ICorePoint> getBranchCorners_RelativeToCanvasOriginAnchor() const;
    static void printDirections(const std::vector<ICorePoint>& path);
    bool isRootBranch() const;

    void shiftBranchCorners_X(double delta_x);
    void shiftBranchCorners_Y(double delta_y);
    void shiftBranchToTailCoords(ICorePoint newTailCoords);

    void select();
    void deSelect();
    bool isSelected() const;

    void setSegUnderCursor(ICoreLinkBranchSegment* segment);

    std::vector<ICoreLinkBranch*> getSplitToBranches() const;
    std::vector<ICorePoint> getBranchCorners() const;
    std::vector<ICoreLinkBranchSegment*>& getBranchUISegments();
    ICoreLink* getParentLink() const;
    ICorePoint getTailCoordinates() const;
    ICorePoint getHeadCoordinates() const;

    void setZValue(int minZOrder);

    bool getIsSplittingFromAnotherBranch() const;
    ICoreLinkBranch* getSplittingFromBranch() const;

    ICoreLinkBranchSegment* getSegUnderCursor() const;
    ICoreLinkBranchSegmentMover* getBranchSegmentMover() const;
    void ensureBranchTailLaysOnBranchingLink();

    static void mergeCollinear_pathUnusedYet(std::vector<ICorePoint>& path);
    void mergeCollinear_currentUsedPath();

    static bool isCollinear(const ICorePoint& a, const ICorePoint& b, const ICorePoint& c, double eps = 1e-8);
    static bool segmentsIntersect(const ICorePoint& p1, const ICorePoint& p2,
                                  const ICorePoint& q1, const ICorePoint& q2,
                                  ICorePoint& intersection);
    static void removeLoops(std::vector<ICorePoint>& path);
    static void normalizeRadAngle(double& angle);

    void setAllowOptimizationAtPortMove(const bool& newAllow);
    bool isOptimizationAllowedAtPortMove() const;

    void setIsBranchDirectPath(bool newIsBranchDirectPath);
    void setParentLink(ICoreLink* newParentLink);

    void resetBranchMigrationOrder();
    void incrementBranchMigrationOrder();
    int getMigrationOrder() const;

    bool isPathFreeOfObstacles(const std::vector<ICorePoint>& originalPath) const;
    static bool lineIntersectsRect(const ICoreRect& rect, const ICorePoint& p1, const ICorePoint& p2);

    void printConnectionDetails() const;

    void resetToInitialState(ICoreLink* parentLink = nullptr);
    void kill();
    void setAlive();
    bool isAlive() const;

    ~ICoreLinkBranch();

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreBlockSolverEnvironment.h#

src/ICoreBlocks/ICoreModel/SolverEnvironments/ICoreBlockSolverEnvironment.h

ICoreBlockSolverEnvironment#

ICoreBlockSolverEnvironment.h:13 · class · pImpl · nested ExportedState · 94 declaration(s)

class ICoreBlockSolverEnvironment {
public:

    // ------------------------------------------------------
    // Constructor
    // ------------------------------------------------------
    explicit ICoreBlockSolverEnvironment(ICoreBlock* parentBlock);

    // DECLARED here, DEFINED in the .cpp: the residue below is a unique_ptr to an
    // incomplete Impl, and every one of the 286 derived blocks destroys this base.
    virtual ~ICoreBlockSolverEnvironment();

    // ------------------------------------------------------
    // Solver Order
    // ------------------------------------------------------
    // Assigned by the build (ICoreModelBuild::assignSolverOrders), which walks the
    // diagram as a graph: a block's order is one more than the highest order among
    // its sources, a cycle is opened at the output of a block with no direct
    // feedthrough, and a cycle with nowhere to open is an algebraic loop. The block
    // itself only stores the result.
    void setSolverOrder(const int& order);

    // ------------------------------------------------------
    // State Space
    // ------------------------------------------------------
    void initializeStateSpace_Continuous(const ICoreMatrix& A, const ICoreMatrix& B, const ICoreMatrix& C, const ICoreMatrix& D);
    void initializeStateSpace_Continuous(const ICoreMatrix &A, const ICoreMatrix &Bu, const ICoreMatrix &Bf,
                                        const ICoreMatrix &C, const ICoreMatrix &Du, const ICoreMatrix &Df);
    void initializeStateSpace_Discrete(const ICoreMatrix &A, const ICoreMatrix &B, const ICoreMatrix &C,
                                       const ICoreMatrix &D, const double &Ts);
    void initializeStateSpace_Discrete(const ICoreMatrix &A, const ICoreMatrix &Bu, const ICoreMatrix &Bf,
                                       const ICoreMatrix &C, const ICoreMatrix &Du, const ICoreMatrix &Df,
                                       const double &Ts);
    void discretize();
    // Clears stateSpace_cont back to its default-constructed (empty) value and drops the
    // valid flag. Needed by blocks whose state-space representability depends on a config
    // that can change between runs -- Gain, whose "Multiplication Type" is only expressible
    // as y = D*u in some modes -- because the flag is otherwise sticky: once a suitable
    // config had set it, a later unsuitable one would leave callers merging stale matrices.
    void clearStateSpace_cont();
    // Virtual so a block can refuse to hand out a state space its current config does not
    // actually satisfy (see the Gain override), rather than returning a stale or meaningless one.
    [[nodiscard]] virtual ICoreStateSpace getStateSpace_cont() const;
    [[nodiscard]] ICoreStateSpace getStateSpace_disc() const;
    // True once initializeStateSpace_Continuous has been called at least once. Blocks that never
    // model continuous dynamics (Gain, Sum, Mux, ...) leave stateSpace_cont at its default-constructed
    // 1x1 zero matrices, which is shape-valid but mathematically meaningless — callers reasoning about
    // stateSpace_cont (e.g. series/parallel merging) must gate on this rather than inspecting matrix sizes.
    [[nodiscard]] bool hasValidStateSpace_cont() const;

    // ------------------------------------------------------
    // Reset
    // ------------------------------------------------------
    void resetSolverOrder();
    void clearInternalStates();
    void assignHomeTreeNodeSampling();

    // ------------------------------------------------------
    // Block Sampling
    // ------------------------------------------------------
    void assignSamplingTime();

    // ------------------------------------------------------
    // Solve Step
    // ------------------------------------------------------
    void setInitialState(const ICoreMatrix& initialState);
    void solve(const double &tn);

    // ------------------------------------------------------
    //      Ports signal size
    // ------------------------------------------------------
    virtual void initializePortSignalSize();
    virtual bool verifyInitializedPortSignals();

    // Port sizing runs as a convergence loop (ICoreModelBuild::initializePortsSignalMatrixSize),
    // so the per-block line initializePortSignalSize() logs would otherwise be repeated once per
    // block per pass. The build turns it off after the first pass and back on when it is done.
    static void setPortSizingLogEnabled(const bool& enabled);

    // For an override that logs its own sizing failures. Anything a block reports from
    // initializePortSignalSize() is reported once per pass unless it asks this first --
    // and a PERMANENT failure (no runtime, code that will not compile) is reported on
    // every one of them, which is how the diagnostics panel came to receive thousands of
    // copies of two lines and take the GUI thread down with it.
    [[nodiscard]] static bool isPortSizingLogEnabled();

    // ------------------------------------------------------
    //      Block Config
    // ------------------------------------------------------
    virtual void loadBlockConfig();

    // ------------------------------------------------------
    //      Block Simulation
    // ------------------------------------------------------
    virtual ICoreMatrix compute_f(const ICoreMatrix& x, const std::vector<ICoreMatrix>& u, const double& t);
    virtual std::vector<ICoreMatrix> compute_h(const ICoreMatrix& x, const std::vector<ICoreMatrix>& u, const double& t);
    virtual ICoreMatrix compute_f_discrete(const ICoreMatrix& x, const std::vector<ICoreMatrix>& u, const double& t);
    virtual std::vector<ICoreMatrix> compute_h_discrete(const ICoreMatrix& x, const std::vector<ICoreMatrix>& u, const double& t);
    virtual void onSolverFinish();

    // The earliest time STRICTLY after t at which this block's output is known in
    // advance to be discontinuous (a Step's step time, a pulse edge, a table
    // breakpoint), or +infinity. A variable-step solver lands the clock on it --
    // with a short approach step first, so the jump is not smeared across a whole
    // step -- and restarts its step size after it. Only a block whose event times
    // are a function of its configuration and the clock can answer; one whose
    // discontinuity depends on a signal (Saturation, Relay) leaves the default.
    // Called once per step per block while a variable-step run is in progress,
    // after loadBlockConfig(). See docs/source/reference/solver-math.md.
    [[nodiscard]] virtual double nextDiscontinuityTimeAfter(const double& t) const;

    // Whether the times this block announces above are SCHEDULED HITS rather
    // than jumps. A hit is landed on exactly, with no approach step before it,
    // which is what Simulink's variable-step solvers do for a Hit Scheduler
    // (measured: 1.0 straight to 1.2345). A time that any unflagged block also
    // announces keeps the approach step. A block
    // whose hit times come from its INPUT stores them from compute_h -- the
    // question above is asked after every step is solved -- and answers the
    // earliest one still ahead. Off by default; a block turns it on once.
    void setAnnouncesScheduledHits(const bool& scheduled);
    [[nodiscard]] bool announcesScheduledHits() const;

    // ------------------------------------------------------
    //      Named states (the owner side of State Reader / State Writer)
    // ------------------------------------------------------
    // The states another block may read or write through this one, by name, the
    // way Simulink's State Reader and State Writer reach an owner block's state.
    // Empty -- the default -- when this block cannot own state: Simulink refuses
    // a continuous Transfer Fcn, Memory, Zero-Order Hold, Rate Transition and
    // every stateless block as an owner, and so does this library. Every owner
    // here has ONE state and names it "", Simulink's default StateName.
    [[nodiscard]] virtual std::vector<std::string> stateNames() const;
    // The named state as it stands now; false for a name this block does not
    // have. Read between two solves it is the state the next solve starts from:
    // before this block's solve in a step that is x[k], after it x[k+1] -- what
    // a State Reader sees when it runs before or after its owner. The layout is
    // the owner's: signal-shaped when each signal entry has one state (Unit
    // Delay, Integrator, Discrete-Time Integrator), [K x n] when each of the K
    // entries has n (one row per entry, the layout every export declares, so a
    // Delay of length 3 on a scalar reads [1 x 3]), and [n x 1] for a
    // state-space block.
    [[nodiscard]] virtual bool readState(const std::string& name, ICoreMatrix& value) const;
    // Replaces the named state; the next solve starts from it. A 1x1 value is
    // written to every element, as a State Writer expands a scalar; otherwise it
    // must have the shape readState() returns, except that a vector may be given
    // as a row or a column. False, with nothing written, for an unknown name or a
    // value that does not fit.
    virtual bool writeState(const std::string& name, const ICoreMatrix& value);
    // "" when this block can own state; otherwise the refusal, in Simulink's
    // words: "Block of type '<type>' cannot be a state owner block".
    [[nodiscard]] std::string stateOwnerRefusal() const;
    // `like` refilled from `value` by the writeState() rules above; false when
    // the value does not fit.
    [[nodiscard]] static bool fitStateValue(const ICoreMatrix& value, const ICoreMatrix& like, ICoreMatrix& fitted);
    // Where this owner keeps its named state in one export target's core, for a
    // State Reader's or Writer's code (FEATURES_TO_ADD.md BF15.4): `elements`
    // names each element of readState()'s [rows x cols] layout, row-major, as code
    // at the core's scope reads and assigns it; `assign` is the operator a write
    // uses (a registered HDL state takes "<="); `seed` gives the state its initial
    // value, and marks it given, when the owner has not run yet -- empty when the
    // core seeds the state where it declares it. `language` is an
    // ICoreCodeEngine::CODE_TYPE_* name and `blockFuncName` this block's name in
    // that core. False -- the default -- when that core cannot reach the state.
    struct ExportedState {
        std::vector<std::string> elements;
        std::string assign = "=";
        std::string seed;
        std::size_t rows = 0;
        std::size_t cols = 0;
    };
    [[nodiscard]] virtual bool exportedState(const std::string& language, const std::string& blockFuncName,
                                             ExportedState& state) const;
    // An owner whose state IS the solver's x (Integrator, the two state-space
    // blocks) turns this on once, and the three virtuals above then answer for
    // it: one state named "", read and written as the x the next solve starts
    // from. A block keeping its state anywhere else overrides them instead.
    void setExposesSolverState(const bool& exposes);
    // That x -- x(n+1) on the discrete path, x(n) on the continuous one, the
    // state the joint scheme gathers -- and its replacement.
    [[nodiscard]] ICoreMatrix solverStateForNextStep() const;
    void setSolverStateForNextStep(const ICoreMatrix& x);

    // ------------------------------------------------------
    //      One copy of the state per slice (a For Each subsystem)
    // ------------------------------------------------------
    // A For Each subsystem runs its blocks once per slice of its inputs every
    // step, and each slice has its own copy of every state: R2026a gives three
    // independent Unit Delays for a 3-element input. The block objects are
    // shared by the slices, so the copies are kept here. Before a slice runs, its
    // copy is loaded; after it, the copy is stashed again under the same key (the
    // slice, and the slices of every For Each around it). A key never stashed in
    // this run loads the state the block had at its first load of the run, which
    // is its seeded initial state.
    //
    // A copy holds the solver's own bookkeeping (x, the last-solved time, the
    // inputs and outputs it remembers), the values on the block's output ports,
    // so a block that holds its output holds each slice's, and the block's OWN
    // state -- whatever it keeps outside x -- through the three virtuals below.
    void stashSliceState(const std::string& key);
    void loadSliceState(const std::string& key);
    // Forgets the stashed copies whose key starts with `prefix` ("" forgets them
    // all), so those slices start again from the initial state: an enclosing
    // subsystem's reset re-seeds every slice inside it, not only the live one.
    void dropSliceStates(const std::string& prefix);
    // Whether every state of this block can be copied that way. The default
    // answers from the joint scheme's contract (setOutputHasSideEffects): a
    // block whose output is a pure function of (x, u, t) keeps no state outside
    // x, and copies. A discrete-only block, or one with side effects, keeps its
    // state in its own members, and answers false unless it overrides these:
    // a For Each holding one is refused by name rather than letting its slices
    // share one state.
    [[nodiscard]] virtual bool copiesStatePerSlice() const;
    // The state kept outside x, and its replacement, as matrices of the block's
    // choosing. Empty, and a no-op, by default.
    [[nodiscard]] virtual std::vector<ICoreMatrix> ownSliceState() const;
    virtual void setOwnSliceState(const std::vector<ICoreMatrix>& state);

    // ------------------------------------------------------
    //      Joint integration (the simulator's continuous coupling)
    // ------------------------------------------------------
    // A block whose compute_f is a real state derivative says so -- the base answers
    // true on its own once initializeStateSpace_Continues() has run, so only a block
    // with continuous states and NO state space (the parameter-varying filters) needs
    // the setter. Under joint coupling these states form the global state vector.
    void setHasContinuousStates(const bool& has);
    [[nodiscard]] bool hasContinuousStates() const;
    // The same, less the states that never move: a feedthrough block that seeds a
    // placeholder continuous state space so model reduction can read it (Gain's
    // y = D*u, A = 0 and B = 0) answers hasContinuousStates() but has no state that
    // changes. What a rule about continuous DYNAMICS asks (FEATURES_TO_ADD.md BF12.8:
    // an atomic subsystem with a rate refuses continuous blocks, not Gains). A state
    // the block declared with setHasContinuousStates is taken at its word.
    [[nodiscard]] bool hasMovingContinuousStates() const;
    // A block whose compute_h is NOT a pure function of (x, u, t) -- it draws a
    // random number, latches, logs, records, asserts -- must say so: joint coupling
    // evaluates pure outputs at every intermediate stage of a step, and would run
    // such a side effect several times per step. Sinks and scopes are skipped by
    // the simulator regardless. Default: pure.
    void setOutputHasSideEffects(const bool& has);
    [[nodiscard]] bool outputHasSideEffects() const;
    // DIRECT FEEDTHROUGH: whether this block's output at an instant depends on its
    // input at the SAME instant. It decides two things. The build opens every
    // feedback loop at the output of a block WITHOUT it (an Integrator, a strictly
    // proper Transfer Function, a Unit Delay), and reports a loop that has no such
    // block as an algebraic loop; and the joint scheme writes such a block's output
    // from its state BEFORE the ordered sweep, so its consumers never read it stale
    // whatever the solve order. Answered, in this precedence: a declaration made
    // with the setter; else, once a continuous state space is registered, whether
    // its Du or Df has a nonzero entry; else, once a discrete state space is
    // registered, the same on its matrices; else false for a discrete-only block
    // (it keeps its state inside compute_h and is taken to hold its output, so a
    // loop through it runs with the one-sample delay it always had) and true for
    // any other (a stateless map of its inputs). So a linear block gets the right
    // answer for free; a block whose output is a latched or delayed copy of its
    // input, computed outside any state space (Unit Delay, Memory, Transport
    // Delay), says so explicitly all the same -- in loadBlockConfig() when the
    // answer depends on a parameter. A wrong "false" resolves a real algebraic loop
    // with a one-sample delay silently; a wrong "true" refuses a diagram that would
    // run.
    void setHasDirectFeedthrough(const bool& has);
    [[nodiscard]] bool hasDirectFeedthrough() const;
    // The state at the start of the current step, x(n-1).
    [[nodiscard]] const ICoreMatrix& stateAtStepStart() const;
    // Copies every connected source signal onto this block's input ports and returns
    // them in port order (step 1 of solve(), reused by the joint scheme's sweeps).
    std::vector<ICoreMatrix> readInputPorts();
    // Writes outputs to the output ports in port order (step 3 of solve()); false, with
    // the run stopped, when the count does not match. A subsystem block's outputs are
    // written by its output gate instead, so it is a no-op there.
    bool writeOutputPorts(const std::vector<ICoreMatrix>& outputs);
    // One stage sweep: outputs from a TRIAL state at an intermediate time, written to
    // the ports, no bookkeeping. Only for a block whose output is pure.
    void evaluateOutputsAt(const ICoreMatrix& xTrial, const double& t);
    // The derivative at a trial state, from whatever the ports hold now.
    [[nodiscard]] ICoreMatrix derivativeAt(const ICoreMatrix& xTrial, const double& t);
    // The end of a joint step: adopt xNew as x(n), compute and write the outputs at
    // tn, and do everything solve() does after its integration (time-budget check,
    // bookkeeping). solve() itself stays the per-block path.
    void commitJointStep(const ICoreMatrix& xNew, const double& tn);

    // ------------------------------------------------------
    //      Coder
    // ------------------------------------------------------
    virtual std::string generateBodyCode_Python();
    virtual std::string generateParamsCode_Python(const std::string& blockFuncName) const;

    virtual std::string generateBodyCode_Matlab();
    virtual std::string generateParamsCode_Matlab(const std::string& blockFuncName) const;
    virtual std::string generateBodyCode_Java();
    virtual std::string generateParamsCode_Java(const std::string& blockFuncName) const;
    virtual std::string generateBodyCode_Rust();
    virtual std::string generateParamsCode_Rust(const std::string& blockFuncName) const;
    virtual std::string generateBodyCode_C();
    virtual std::string generateParamsCode_C(const std::string& blockFuncName) const;
    virtual std::string generateStateCode_C(const std::string& blockFuncName) const;   // persistent C state fields
    virtual std::string generateBodyCode_Cpp();
    virtual std::string generateParamsCode_Cpp(const std::string& blockFuncName) const;

    virtual std::string generateBodyCode_VHDL();
    virtual std::string generateParamsCode_VHDL(const std::string& blockFuncName) const;
    virtual std::string generateStateDeclCode_VHDL(const std::string& blockFuncName) const;    // persistent architecture-scope state signals
    virtual std::string generateStateResetCode_VHDL(const std::string& blockFuncName) const;   // rst-branch seeding for the above
    virtual std::string generateBodyCode_Verilog();
    virtual std::string generateParamsCode_Verilog(const std::string& blockFuncName) const;
    virtual std::string generateStateDeclCode_Verilog(const std::string& blockFuncName) const;    // persistent module-scope state regs
    virtual std::string generateStateResetCode_Verilog(const std::string& blockFuncName) const;   // rst-branch seeding for the above
    virtual std::string generateBodyCode_SystemVerilog();
    virtual std::string generateParamsCode_SystemVerilog(const std::string& blockFuncName) const;
    virtual std::string generateStateDeclCode_SystemVerilog(const std::string& blockFuncName) const;    // persistent module-scope state regs
    virtual std::string generateStateResetCode_SystemVerilog(const std::string& blockFuncName) const;   // rst-branch seeding for the above

    virtual std::string generateDeclCode_PLC_ST();
    virtual std::string generateBodyCode_PLC_ST();
    virtual std::string generateParamsCode_PLC_ST(const std::string& blockFuncName) const;

    // ------------------------------------------------------
    //      Getters
    // ------------------------------------------------------
    void setDiscreteOnlyBlock(const bool& newDiscreteOnlyBlock);
    [[nodiscard]] bool isDiscreteOnlyBlock() const;
    // Moves the block's own clock (its last-solved time) by delta without touching
    // its state. The simulator calls it when an infinite run rewinds the global
    // clock, so the next solve still sees one period rather than a step back to
    // the start of time.
    void shiftLastSolvedTime(const double& delta);
    [[nodiscard]] int getSolverOrder() const;
    [[nodiscard]] double getSamplingTime() const;
    [[nodiscard]] ICoreBlock* getParentBlock() const;
    [[nodiscard]] ICoreMatrix& getConfig_matrix(const std::string& key);
    [[nodiscard]] std::string& getConfig_string(const std::string& key);
    [[nodiscard]] std::vector<ICorePort*> getInputPorts() const;
    [[nodiscard]] std::vector<ICorePort*> getOutputPorts() const;

    [[nodiscard]] double get_tn_1() const;
    [[nodiscard]] double get_dt() const;

    void printConfigMap_double() const;
    void printConfigMap_string() const;

    // ------------------------------------------------------
    //      Tunable configs (FEATURES_TO_ADD.md BF11.4)
    // ------------------------------------------------------
    // A config the block marked tunable (ICoreBlockConfigVariable::setIsTunable)
    // can take a new value while a run is live. The write goes into the run's
    // LOADED copy, the map getConfig_matrix() reads, and never into the config
    // variable: nothing persists past the run, as in Simulink (BF11.1), and the
    // next run loads the configured value again. ICoreModelSimulator::
    // writeTunableConfig queues a write; the simulator applies the queue at the
    // start of the next solve.
    //
    // The reason a write to `configName` must be refused, or "" when it may be
    // applied: the config must exist, be tunable and loaded, keep its size (the
    // block's ports were sized from it), and pass the block's own check below.
    [[nodiscard]] std::string refuseTunableWrite(const std::string& configName, const ICoreMatrix& value) const;
    // Replaces the loaded value. The caller has asked refuseTunableWrite first.
    void applyTunableWrite(const std::string& configName, const ICoreMatrix& value);
    // The block's own cross-check, run on every write: Saturation refuses an
    // upper limit below its lower one. Returns "" to accept.
    using TunableWriteCheck = std::function<std::string(const std::string& configName, const ICoreMatrix& value)>;
    void setTunableWriteCheck(TunableWriteCheck check);

    // The per-block rate config EVERY block carries (created in the constructor).
    // Public because the Simulink bridge maps it onto Simulink's SampleTime for
    // every block type rather than per entry — the two share a convention:
    // <= 0 inherits the surrounding rate, > 0 is an explicit period.
    static const std::string CONFIG_SAMPLING_TIME;

    // "Is a solver running right now?", asked from inside ICoreModel.
    //
    // ⚠ It lives HERE, on the block's solver environment, for a layering reason
    // and not a design one: ICoreModel (L3) sits below ICoreSimulation (L4), and
    // this .cpp is the ONE file in ICoreModel that already includes
    // ICoreModelSimulator. Every other file in the layer asks the question
    // through this forwarder rather than reaching upward a second time.
    [[nodiscard]] static bool isSimulationLive();

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICorePortSolverEnvironment.h#

src/ICoreBlocks/ICoreModel/SolverEnvironments/ICorePortSolverEnvironment.h

---- the port's signal type, as the solver and the generators want it ----

The KIND is what a per-step decision switches on -- the write-time rule (quantize, T3.1), the value carrier (T3.2), the verification stimulus -- and it is a table lookup from a string. Doing that lookup on every step of every port would be silly, so it is cached here: once at construction and again whenever ICorePort::setType() re-types the port (T2.12).

ICorePortSolverEnvironment#

ICorePortSolverEnvironment.h:11 · class · pImpl · 16 declaration(s)

class ICorePortSolverEnvironment {
public:
    explicit ICorePortSolverEnvironment(ICorePort* parentPort);

    // ---- the port's signal type, as the solver and the generators want it ----
    //
    // The KIND is what a per-step decision switches on -- the write-time rule
    // (quantize, T3.1), the value carrier (T3.2), the verification stimulus --
    // and it is a table lookup from a string. Doing that lookup on every step of
    // every port would be silly, so it is cached here: once at construction and
    // again whenever ICorePort::setType() re-types the port (T2.12).
    [[nodiscard]] const std::string& getSignalTypeId() const;
    [[nodiscard]] ICoreSignalType::Kind getSignalKind() const;

    // Called by ICorePort::setType(). Not for anyone else: the cache is only
    // ever wrong if the port's type changed, and the port is the only thing that
    // can change it.
    void refreshSignalType();

    void setPortSignalSize(const size_t& newNumOfRows, const size_t& newNumOfColumns);
    void resetPortSignal();
    size_t getPortSignalNumOfRows() const;
    size_t getPortSignalNumOfColumns() const;

    ICoreMatrix* getSignal() const;

    // ---- the side carrier, for the two kinds a matrix cannot hold ----------
    //
    // getSignal() is unchanged for every numeric kind and is what the whole
    // block library reads. A String or Bus port's matrix is a 1x1 zero
    // placeholder and the value is here instead.
    [[nodiscard]] const std::string& getStringSignal() const;
    void setStringSignal(const std::string& text);
    [[nodiscard]] ICoreSignalValue* getBusSignal() const;

    // ---- the write-time rule (T3.1) ---------------------------------------
    //
    // Applies this port's KIND to the doubles now in its matrix: Bool becomes
    // 0 or 1, IntN truncates toward zero and wraps to N bits, Single rounds to
    // float, Floating is left alone. Called at the one place a value enters a
    // port -- the output-port write -- and at the verifier's injection path,
    // which is the other way a value gets in.
    //
    // NaN or infinity into an integer or boolean port becomes 0, and says so
    // ONCE per port per run: it is a per-sample event, and a diagnostic per
    // sample would be the only thing left in the log.
    void quantizeSignalToType();

    // Clears the once-per-run diagnostic latch. Called by the build, which is
    // what makes "once per run" mean a run and not a process.
    void beginRun();

    void resetToInitialState();

    ~ICorePortSolverEnvironment();

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreTreeNodeSolverEnvironment.h#

src/ICoreBlocks/ICoreModel/SolverEnvironments/ICoreTreeNodeSolverEnvironment.h

ICoreTreeNodeSolverEnvironment#

ICoreTreeNodeSolverEnvironment.h:7 · class · pImpl · 8 declaration(s)

class ICoreTreeNodeSolverEnvironment {
public:
    explicit  ICoreTreeNodeSolverEnvironment();

    void setMaxSolverOrder(const int &newMaxSolverOrder);

    void addMatchedOrderBlocks(const int& order, const std::vector<ICoreBlock*>& blocks);

    int getMaxSolverOrder() const;
    std::unordered_map<int, std::vector<ICoreBlock *>> getBlocksToSolveMap() const;
    std::vector<ICoreBlock *> getMatchingOrderBlocksToSolveList(const int &order) const;

    void resetToInitialState();

    ~ ICoreTreeNodeSolverEnvironment();

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreUnits.h#

src/ICoreBlocks/ICoreModel/Units/ICoreUnits.h

ICoreUnits#

ICoreUnits.h:34 · class · nested Row · 12 declaration(s)

Units on wires: Simulink's Unit parameter, as R2026a has it (measured).

class ICoreUnits {
public:
    // The value a gate's `Unit` config takes by default, and the spelling of "no
    // unit assigned".
    static const std::string INHERIT;

    // What `block` assigns: its `Unit` config when it is a subsystem input or
    // output gate and the config is not `inherit`, else "".
    static std::string assignedUnit(const ICoreBlock* block);

    // The unit `port` carries once inheritance is resolved, or "" when none
    // reaches it: an assigning gate's own ports carry its unit, a gate at
    // `inherit` and a face port carry what arrives through the pairing, and an
    // input port carries its source's.
    static std::string unitOf(const ICorePort* port);

    // The unit a link carries: its source port's.
    static std::string unitOf(const ICoreLink* link);

    // The unit ARRIVING at a gate port, before the gate's own assignment: for an
    // input gate's output port, what reaches the face input it is paired with;
    // for an output gate's input port, what its source carries. "" when none.
    static std::string arrivingUnit(const ICorePort* gatePort);

    // The unit the consumers of `outputPort` require, read DOWNSTREAM through
    // wires and gates only, as R2026a back-propagates it (a Unit Conversion block's
    // output compiles to what the Inport it feeds assigns). "" when no assigning
    // gate is reached that way.
    static std::string requiredUnit(const ICorePort* outputPort);

    // ---- The unit table: one row per unit, its quantity, and its affine map onto
    // that quantity's SI base unit, x_SI = scale * x + offset.
    //
    // `name` is ICore's own spelling, offered by the Unit Conversion block's lists;
    // `simulinkName` is how R2026a's unit database spells the same unit (measured,
    // FEATURES_TO_ADD.md BF20.3), the spelling a gate's `Unit` uses, or "" when the
    // database holds no such unit.
    struct Row {
        const char* name;
        const char* simulinkName;
        const char* quantity;
        double scale;
        double offset;
    };
    static const std::vector<Row>& table();
    static const Row* findByName(const std::string& name);
    static const Row* findBySimulinkName(const std::string& simulinkName);

    // y = a*u + b taking a value in `from` to `to`, both rows of the table.
    static void affine(const Row& from, const Row& to, double& a, double& b);

    // ---- The build's pass (ICoreModelBuild, after the type checks): every gate
    // under `home` whose assigned unit differs from the one arriving converts it
    // when `convert` is on and both units are table rows of one quantity, and is
    // reported in `messages` otherwise -- R2026a's three diagnostics, which are
    // warnings and never refusals. Each conversion is reported too.
    static void settle(const ICoreSubsystemTreeNode* home, bool convert, std::vector<std::string>& messages);

    // The conversion settle() decided for a gate port (an input gate's output port,
    // or an output gate's input port): false when its value passes unchanged.
    static bool conversionAt(const ICorePort* gatePort, double& a, double& b);

    // Every gate port settle() decided to convert at, in the order it found them.
    static std::vector<const ICorePort*> convertingPorts();
};
};

ICoreVariablesSpace.h#

src/ICoreBlocks/ICoreModel/VariablesSpace/ICoreVariablesSpace.h

ICoreVariablesSpace#

ICoreVariablesSpace.h:17 · class · pImpl · 31 declaration(s)

Backend model: a storage of variables.

class ICoreVariablesSpace {
public:
    explicit ICoreVariablesSpace(ICoreSubsystemTreeNode* parent);

    // The one variables space every console line reads and writes: no parent
    // tree node, created on first use, alive for the whole process. Owned here
    // (L3) so the console interpreter in ICoreCoder and the registry in Shell
    // both reach it downward; ICoreOtherObjectsRegistry::getGlobalVariablesSpace
    // is a forwarder to this.
    static ICoreVariablesSpace* global();

    // Layer 4: canonical "remove me from the live model graph". A variables space is held by its
    // parent tree node via a single pointer (no children vector), and its UI now lives in the left
    // fixed panel (not the canvas), so this is just a selection detach. Idempotent.
    void detachFromModel();

    // -------------------------------------------------------------------------------------------
    // View registry. Each ICoreVariablesSpaceView attaches itself on construction and detaches on
    // destruction. refreshAllUIs() tells every attached view to re-read the model.
    // -------------------------------------------------------------------------------------------
    void attachUI(ICoreVariablesSpaceView* ui);
    void detachUI(ICoreVariablesSpaceView* ui);
    void refreshAllUIs();

    // Read access for views to render rows from the model.
    [[nodiscard]] const std::vector<ICoreVariablesSpaceVariable*>& getAllVariables() const;

    // Commit helpers called by a view when a cell loses focus. The model validates/writes the data
    // and then re-syncs all views (so a rejected duplicate name is reverted everywhere). Returns
    // true only when the stored data actually changed (so callers can autosave just on real edits;
    // a no-op re-entry or a rejected duplicate name returns false).
    bool commitVariableName(ICoreVariablesSpaceVariable* var, const std::string& candidate);
    bool commitVariableValue(ICoreVariablesSpaceVariable* var, const std::string& value);

    ICoreVariablesSpaceVariable* createNewVariable();
    bool isVariableNameUnique(const std::string &nameToCheck, const ICoreVariablesSpaceVariable* variableToExclude = nullptr) const;

    // Programmatic declaration (e.g. from the command console, not a table view). Upserts by name:
    // updates the value if the name already exists, otherwise fills the first empty placeholder row
    // or appends a new one. Re-syncs all attached views. Returns false only for an empty name.
    bool declareVariable(const std::string& name, const std::string& value);

    // Programmatic declaration of a recorded signal, the Signal Recorder block's
    // way in. Same upsert-by-name semantics as declareVariable() -- it just
    // serialises the series to its canonical timeseries(...) string first, so
    // the stored entry is an ordinary variable that happens to type as
    // "Time Series". Returns false for an empty name or an invalid series
    // (mismatched time/value lengths), never storing a half-formed one.
    //
    // Not thread safe: it re-syncs the attached views, so it must be called on
    // the GUI thread. A block recording from the solver thread has to marshal
    // (see the Signal Recorder's onSolverFinish).
    bool declareTimeSeries(const std::string& name, const ICoreTimeSeries& series);

    void deleteVariable(ICoreVariablesSpaceVariable* varToDelete);

    void clearAllVariables();

    void autoAdjustNumOfRows();

    // -------------------------------------------------------------------------------------------
    // Read-only access for the rest of the backend.
    //
    // The variables space is a pure "space of variables": it is edited *only* through its views.
    // Other modules must never mutate it; they may only read a variable's value/type by name
    // through this API. Only variables with a non-empty name count as defined (empty placeholder
    // rows are skipped).
    // -------------------------------------------------------------------------------------------
    [[nodiscard]] bool hasVariable(const std::string& name) const;
    [[nodiscard]] std::string getVariableValue(const std::string& name) const;   // "" if not found
    [[nodiscard]] std::string getVariableType(const std::string& name) const;    // "" if not found
    [[nodiscard]] const ICoreVariable* getVariable(const std::string& name) const; // nullptr if not found
    [[nodiscard]] std::vector<std::pair<std::string, std::string>> getAllDefinedVariables() const;

    // Recorded-signal reads. getTimeSeries() returns false when the name is
    // unknown, holds something that is not a time series, or holds one whose
    // stored string no longer parses -- callers get an empty series in every
    // failure case and never a partially filled one.
    [[nodiscard]] bool getTimeSeries(const std::string& name, ICoreTimeSeries& out) const;

    // Names of every entry currently typing as "Time Series", in table order.
    // This is what the math tool windows list as their available signals.
    [[nodiscard]] std::vector<std::string> getAllTimeSeriesNames() const;

    [[nodiscard]] ICoreCanvasObjectState* getState() const;

    void updateToState(ICoreCanvasObjectState *desiredState);

    ICoreSubsystemTreeNode* getParent() const;
    [[nodiscard]] std::string getName() const;
    [[nodiscard]] std::string getPath() const;

    void select();
    void deSelect();

    bool isEmpty() const;

    bool isSelected() const;

    const std::string& getClassID();

    ~ICoreVariablesSpace();

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreVariablesSpaceVariable.h#

src/ICoreBlocks/ICoreModel/VariablesSpace/ICoreVariablesSpaceVariable.h

ICoreVariablesSpaceVariable#

ICoreVariablesSpaceVariable.h:11 · class · pImpl · 8 declaration(s)

Pure data model for a single variables-space entry.

class ICoreVariablesSpaceVariable {
public:
    explicit ICoreVariablesSpaceVariable(ICoreVariablesSpace* parentSpace);

    // Data-only mutators (no UI). Type is derived from the value by the underlying ICoreVariable.
    std::string setName(const std::string& newName) const;
    std::string setValue(const std::string& newValue);

    ICoreVariable* getSyntraVariable() const;

    [[nodiscard]] std::string getName() const;
    [[nodiscard]] std::string getValue() const;
    [[nodiscard]] std::string getType() const;

    ~ICoreVariablesSpaceVariable();

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreWorkspaceObjects.h#

src/ICoreBlocks/ICoreModel/VariablesSpace/ICoreWorkspaceObjects.h

ICoreWorkspaceObjects#

ICoreWorkspaceObjects.h:13 · class · 1 declaration(s)

How a run leaves an OBJECT in the global variables space -- an iddata, a linearization result -- rather than a signal (FEATURES_TO_ADD.md BF18.2, decision D14: a Record).

class ICoreWorkspaceObjects {
public:
    // Declares `name` = `record` in the global variables space, on the GUI thread, as the
    // record's struct(...) storage form. A second declare of the name replaces it.
    static void declareRecord(const std::string& name, const ICoreRecord& record, const std::string& blockLabel);

    // MATLAB's time-domain `iddata` as a record. `outputData` is N x ny and `inputData` N x nu,
    // one row per sample and one column per channel; `ts` is the sample time and `tstart` the
    // first sample's time. The four numeric properties are held -- OutputData, InputData, Ts,
    // Tstart, in MATLAB's property order -- and the character ones (Domain 'Time', InterSample
    // 'foh', TimeUnit 'seconds', and the channel names u1.. and y1..) are withheld BY NAME,
    // because a record holds numbers only. false, with `whyNot`, for a row-count mismatch or a
    // sample time that is not a positive finite number, as MATLAB's iddata refuses one.
    static bool iddataRecord(const ICoreMatrix& outputData, const ICoreMatrix& inputData, double ts, double tstart,
                             ICoreRecord& out, std::string* whyNot = nullptr);
};
};

ICoreVariantControls.h#

src/ICoreBlocks/ICoreModel/Variants/ICoreVariantControls.h

ICoreVariantControls#

ICoreVariantControls.h:45 · class · nested Condition, Choice · 9 declaration(s)

Variant controls: which choice of a variant block is active, decided over the global variables space before the build (FEATURES_TO_ADD.md BF3.2, decision D3: Simulink's "update diagram" activation,...

class ICoreVariantControls {
public:
    enum class Mode { Expression, Label, SimCodegen };
    enum class BlockKind { Source, Sink, Start, Subsystem };

    // One condition, evaluated over the global variables space.
    struct Condition {
        bool ok = false;          // false: `refusal` says why
        bool active = false;      // the condition's value, when ok
        std::string refusal;
    };
    static Condition evaluate(const std::string& condition, const std::string& blockPath = std::string());

    // The active choice of a variant block, by its controls in port (or choice)
    // order. `active` is the 0-based choice, or -1 when none is -- which is a
    // refusal unless `allowZero`, when it means every choice is inactive.
    struct Choice {
        bool ok = false;
        int active = -1;
        std::string refusal;
    };
    static Choice activeChoice(const std::vector<std::string>& controls, BlockKind kind,
                               const std::string& blockPath, Mode mode = Mode::Expression,
                               const std::string& activeLabel = std::string(), bool allowZero = false);

    // The controls a config holds, one per choice. Accepts Simulink's cell forms,
    // `{'V==1';'V==2'}` and `{'V==1','V==2'}`, and a plain list, `V==1; V==2`.
    static std::vector<std::string> parseControls(const std::string& text);

    // The config names a variant block uses, spelled once (BF3.2), and their parsers.
    static const std::string CONFIG_VARIANT_CONTROLS;          // "Variant Controls"
    static const std::string CONFIG_VARIANT_CONTROL_MODE;      // "Variant Control Mode"
    static const std::string CONFIG_LABEL_MODE_ACTIVE_CHOICE;  // "Label Mode Active Choice"
    static const std::string CONFIG_ALLOW_ZERO_VARIANT_CONTROLS; // "Allow Zero Variant Controls"
    static Mode modeFromName(const std::string& name);         // "expression" | "label" | "sim codegen switching"

    // A VARIANT SUBSYSTEM (BF3.5): a Subsystem block with "Variant" On. Its choices are the
    // subsystems directly inside it, each with its own "Variant Control" (a condition, or a
    // label in label mode); the face's "Variant Control Mode" and "Label Mode Active Choice"
    // pick among them by activeChoice()'s Variant Subsystem rule -- the first true one. The
    // choices are wired like any blocks, and each output reaches the face's output gate
    // through a Variant Merge, which passes on the active choice's (the owner's ruling,
    // 2026-09-30: wired, with a merge, rather than Simulink's unwired name matching).
    static const std::string CONFIG_VARIANT;                   // "Variant", Off/On, on the face
    static const std::string CONFIG_VARIANT_CONTROL;           // "Variant Control", on each choice
    static bool isVariantSubsystem(const ICoreBlock* face);
    // The choices of a Variant Subsystem, in the order its level holds them.
    static std::vector<const ICoreBlock*> choicesOf(const ICoreBlock* variantFace);
    // Its active choice, or nullptr with the refusal said in `refusal`.
    static const ICoreBlock* activeChoiceOf(const ICoreBlock* variantFace, std::string* refusal = nullptr);
    // Every Variant Subsystem among `blocks` that has no active choice, one line each.
    static std::vector<std::string> refusals(const std::vector<ICoreBlock*>& blocks);

    // A VARIANT ASSEMBLY SUBSYSTEM (the owner's option 1, 2026-10-02): a Variant Subsystem
    // in label mode whose "Variant Choices Specifier", Simulink's VariantChoicesSpecifier,
    // names files. Its choices are then
    // exactly those files, each a Subsystem Reference named after its file with the file's
    // name as its label; ICoreVariantAssembly (ICoreStudio) makes them so after every edit.
    // Empty (the default) is not an assembly, and the specifier is read nowhere else, as
    // R2026a accepts it on an expression-mode Variant Subsystem or a plain subsystem and
    // does nothing with it (measured, 2026-10-02).
    static const std::string CONFIG_VARIANT_CHOICES_SPECIFIER; // "Variant Choices Specifier"
    static bool isVariantAssembly(const ICoreBlock* face);
    // The files a specifier names, in its order, each once: a cell of names, `{'A','B'}`
    // or `{}`, a name with ".icore" added when it has no extension. False, with `why`,
    // for text that is not a cell, as R2026a refuses a bare name (VASErrorInVarSelEval).
    static bool parseChoicesSpecifier(const std::string& text, std::vector<std::string>& files,
                                      std::string* why = nullptr);
    // The label a file's choice gets, and its name: the file's name without folder or
    // extension ("References/Fast.icore" -> "Fast").
    static std::string choiceLabelFor(const std::string& file);
};
};