|
FhSim
3.1.0
Marine systems simulation
|
This guide covers all breaking changes when upgrading from FhSim 2.x (main branch) to FhSim 3.0 (new_structure branch). Most downstream consumers are SimObject plugin libraries — the guide is structured with this audience first.
An automated migration script is available: see Using the migration script below.
v3 API stability. FhSim 3.0 is the current major version. The v3 XML input schema and the SimObject C++ API documented here are the stable target you should build against — the churn described in this guide is the one-time 2.x→3.0 break, not an ongoing state. Any future breaking change will be recorded in this guide with a Before/After mapping (per the project's deprecation policy) and announced on the issue tracker; retired names and attributes are also enforced by the documentation checks. There is no separate deprecation schedule beyond that.
FhSim 3.0 introduces:
FhSim::fhsim CMake target is split into purpose-built components. SimObject plugin authors now link the lighter FhSim::simobject target instead of the full engine.C prefix is removed from core utility classes (e.g., CPrintDuringExec → PrintDuringExec).CPrintDuringExec.h → PrintDuringExec.h).<INTEGRATION> is renamed to <SIMULATION> (backward compatible with deprecation warning). The <Engine> and <Sundials> child elements are replaced by a new unified <Integrator Method="..."> element with structured sub-elements.OdeFcn is now const and no longer takes isMajorTimeStep. Once-per-accepted-step side effects should move to AcceptedStep(double T, const double* X).This section covers the typical consumer: a downstream library that defines SimObject subclasses and is compiled as a shared plugin (MODULE).
Replace FhSim::fhsim with FhSim::simobject in your CMakeLists.txt:
Before (v2):
After (v3):
The FhSim::simobject target provides everything a SimObject plugin needs: the SimObject base class, port definitions, ISimObjectCreator, utilities, and transitive dependencies (FhLib, libxml2, Eigen, and optionally Ogre).
In your conanfile.py or conanfile.txt, bump the FhSim version:
Before:
After:
Note: the first release on the 3.x line is 3.1.0, not 3.0.0 —
fhsim_base/3.0.0is an already-released reference on the 2.x line. Use the caret range[^3.1.0]rather than an exact pin.
Ensure your Conan profile has compiler.cppstd=20.
The following headers have been renamed (the C prefix is removed):
Old #include | New #include |
|---|---|
#include "CPrintDuringExec.h" | #include <fhsim/PrintDuringExec.h> |
#include "CFhSimMgr.h" | #include <fhsim/FhSimMgr.h> |
#include "CExceptions.h" | #include <fhsim/Exceptions.h> |
#include "CAttributePath.h" | #include <fhsim/AttributePath.h> |
#include "CInputReader.h" | #include <fhsim/InputReader.h> |
File names that are unchanged (no rename needed). Most now live in a sub-folder, so check the include path: <fhsim/simobject/SimObjectInclude.h>, <fhsim/simobject/SimObject.h>, <fhsim/simobject/ISimObjectCreator.h>, <fhsim/simobject/ICommonComputation.h>, <fhsim/simobject/ISignalPort.h>, <fhsim/simobject/KalmanObject.h>, <fhsim/simobject/ExternalLink.h>, <fhsim/simobject/PortDefs.h>, <fhsim/simobject/PortStructDefs.h>, <fhsim/NFhSim.h>, <fhsim/ISimObjectLogger.h>.
Note:
dllLib.hwas renamed toDllLib.h. If your SimObject DLL entry point file includes it, update to#include <fhsim/DllLib.h>.
Apply these find-and-replace operations across your C++ source files:
| Old class name | New class name |
|---|---|
CPrintDuringExec | PrintDuringExec |
CFhSimMgr | FhSimMgr |
CExceptions | Exceptions |
CAttributePath | AttributePath |
CInputReader | InputReader |
The string key "CPrintDuringExec" is not renamed by the script. The key is now fhsim::kSimMgrKeyPrinter (value "PrintDuringExec", <fhsim/NFhSim.h>). Read it with creator->GetSharedResource<PrintDuringExec>(fhsim::kSimMgrKeyPrinter).
Classes that are unchanged (no action needed): SimObject, ExternalLink, KalmanObject, ISimObjectCreator, ICommonComputation, ISignalPort, SerialCommonComputation. (IStateUpdater was removed later, see below.)
The protected member variable that stores the object's registered name was renamed in the SimObject base class:
| Old | New |
|---|---|
m_SimObjectName | m_simObjectName |
This only affects code that accesses this member directly by name. Most code uses the public GetName() accessor, which is unchanged.
v3 removed the using std::string; declaration that v2 had at file scope in ISimObjectCreator.h. Any of your own headers that relied on this transitive using declaration will now fail to compile with ‘'string’ was not declared`.
Fix your headers by qualifying the type explicitly:
Fix your **.cpp implementation files** by adding a using declaration after your includes, or by qualifying at usage sites:
Test stubs and mock ISimObjectCreator classes hit this most often — local Creator subclasses defined inside _Test.cpp files often override GetStringParam, GetDoubleParamArray, AddInport, AddOutportIndexed with bare string parameters. Find all occurrences with:
Then qualify each one as std::string.
The v2 test tools (fhSimTestTools/TestSetup.h, testTools::SetUpTest, testTools::RmsComparison) are not in v3. The v3 test library is <fhsim/testtools/FhsimTest.h>. See Test Tools Library for the full API.
CMake link target:
Before:
After:
Note the change from hyphen (-) to underscore (_).
Test code:
| v2 | v3 |
|---|---|
#include <fhSimTestTools/TestSetup.h> | #include <fhsim/testtools/FhsimTest.h> |
testTools::SetUpTest(...) | fill a fhsim::test::TestSpec (ID, simTime, maxRunTime, runtimeConfig) and call fhsim::test::RunTest(spec) |
testTools::RmsComparison(...) | result.CompareRegression(spec) |
<ID>_exp.txt (expected values) | <ID>.ref (limits) and <ID>_ref.csv (expected output) |
CompareRegression reads two files per test ID, in <inPath>/<group>/: <ID>.ref holds the limits (cpu_ref, wall_max, output_rms_max), and <ID>_ref.csv holds the expected output. The RMS tolerance of the v2 comparison is now output_rms_max in the .ref file. Write both files from a run you have checked: set spec.referenceModeOverride = fhsim::test::ReferenceMode::CreateIfMissing once, then commit the files and go back to the default ReadOnly. See Recommended workflow for the file format and the reference modes.
**GetTestRuntimeConfig() is yours to define.** FhSim does not provide it. The test executable builds one fhsim::test::TestRuntimeConfig in its main (test folder from argv, log levels) and returns it from a function that the tests call. FhSim's own tests do this in tests/core/fhSim/Run_test.cpp and TestRuntimeConfigAccess.cpp. See Runtime configuration (TestRuntimeConfig).
Registering the tests with ctest: find_package(FhSim) also loads FhTests.cmake, which provides fh_add_gtest_tests. It adds the ctest entries for a gtest executable. LABELS is required and takes exactly one type from unit, integration, smoke, regression, validation or perf:
See the header of tests/cmake/FhTests.cmake (in the fhsim source) for the other arguments, the test tiers and the timeouts.
If your library compiles source files that include visualization headers (FhCamera.h, FhVisualization.h, etc.), you must explicitly link the FhSim::fhsim_vis component, which provides these headers in v3.
In the CMakeLists.txt that builds your SimObject library:
FhSim::fhsim_vis is a static library, and it requires FhSim::fhsim** (conanfile.py, component fhsim_vis). A plugin (MODULE) that links it takes its own copy of the library and of the engine code it pulls in, with its own static data. A static object that exists once in the host (a singleton) then exists twice, once in the host and once in your plugin. FhSim's own fhsim_vis_dll module does not link fhsim_vis for this reason (src/visual/dll/module/CMakeLists.txt). The classes of the <fhsim/visual/renderer/...> headers (FhCamera, FhVisualization, Hud) are compiled into FhSim::fhsim, and fhsim_vis holds the visualization observers and FhVisDll. Link FhSim::fhsim_vis only into an application that hosts the visualization, not into a plugin that only uses the objects the host gives it. Reach those objects through the shared resources (see below), not through a singleton of your own.FHSIM_FH_VISUALIZATION is a CMake cache variable set by fhsim-variables.cmake (included automatically when find_package(FhSim) is called) when the fhsim package was built with with_visualization=True.
Include form for visualization headers:
The FhSim::fhsim_vis component registers include/ as its include root. Headers must therefore be included with the full fhsim/ prefix:
Visualization class and shared-resource renames:
| Old | New |
|---|---|
#include <CFhCamera.h> | #include <fhsim/visual/renderer/FhCamera.h> |
CFhCamera* cam | FhCamera* cam |
(CFhCamera*)creator->GetSharedResource("CFhCamera") | creator->GetSharedResource<FhCamera>(fhsim::kSimMgrKeyFhCamera) (key "FhCamera", <fhsim/NFhSim.h>; typed, ADR 0013) |
(CFhVis*)creator->GetSharedResource("CFhVis") | creator->GetSharedResource<FhVisualization>(fhsim::kSimMgrKeyFhVisualization), see below |
CInputManager, <CInputManager.h> | Removed in 3.4.0, with OIS: read input with SDL2, see below |
CFhCameraController, <CFhCameraController.h> | Removed in 3.4.0: FhCameraControllerSDL (<fhsim/visual/ui/FhCameraControllerSDL.h>) |
String literals such as "CFhCamera" and "CFhVis" are not renamed by the migration script; use the fhsim::kSimMgrKey* constants of <fhsim/NFhSim.h>.
**The host's visualization object (kSimMgrKeyFhVisualization):** the key holds an FhVisualization under FhVis and fhVisDll, and an FhVisualizationSDL under FhSimUI (<fhsim/NFhSim.h> has the table). Read it with the type of the host you run under, in RenderInit, and handle nullptr:
Keyboard, mouse and joystick input: InputManager is removed (3.4.0). FhSim 3.0 to 3.3 had InputManager (<fhsim/visual/renderer/InputManager.h>, the renamed CInputManager), an OIS wrapper that FhVis's window created. FhSim no longer uses OIS: FhVis and FhSimUI both open SDL2 windows, initialise SDL's video and joystick (game controller) subsystems and pump SDL's events every frame. A SimObject that read a joystick or the keyboard through InputManager reads it from SDL2 instead:
sdl/2.32.10 in its recipe and link SDL2::SDL2 (no transitive SDL2 comes with FhSim);RenderInit (SDL_NumJoysticks, SDL_JoystickOpen, or SDL_GameControllerOpen for a game controller) and read it in RenderUpdate (SDL_JoystickGetAxis, SDL_JoystickGetButton, SDL_JoystickGetHat); for the keyboard, SDL_GetKeyboardState;RenderRelease.Input works only under a host with an SDL window (FhVis, FhSimUI); fhVisDll's embedded panel gets no input.
Before (v2 SimObject source):
After (v3 SimObject source):
if (isMajorTimeStep) inside OdeFcn(...) should move to AcceptedStep(double T, const double* X).PreOdeFcn(T, X) is the pre-step hook. It cannot write states: IStateUpdater is removed. Put a state rule (a limit, a normalisation) in OdeFcn as a derivative saturation or a correction term.PortFunction and IndexedPortFunction are pointers to non-const SimObject member functions (<fhsim/simobject/PortDefs.h>), but OdeFcn is const. A const OdeFcn therefore cannot call your own output-port function. Move the computation into a const helper that both the port function and OdeFcn call, or into a common computation (CommonComputation is const, see below). Use const_cast only as a last resort.TStart, before the first step. The first row of the result file is evaluated from the initial state with the output-port functions, before any integrator step (IntegratorStateProvider::Advance, first call). A port that returns a value which OdeFcn caches in a member can then return NaN or an uninitialised value in the first row. Initialise the member in the constructor or in InitialConditionSetup, or compute the port value from the stateVec it is given.ICommonComputation::ComputeFunction(...) is now const. If your implementation updates cached internals, use mutable members.CommonComputation callback typedef now expects a const SimObject member function.IntegratorOptionsCommon and the kSimMgrKeyIntegratorOptions ("IntegratorOptions") registry key were removed with the unified IntegratorConfig. A SimObject that asked the simulation manager for the integrator options now reads fhsim::IntegratorSetup under fhsim::kSimMgrKeyIntegratorSetup:
Removed (IntegratorOptionsCommon) | Replacement (fhsim::IntegratorSetup) |
|---|---|
GetStartTime() | tStart |
GetEndTime() | tEnd |
GetStepSize() | step (the requested fixed step; 0 when the method steps adaptively) |
GetMethod() | method |
method is the canonical v3 method name, so a comparison against a v2 name has to be updated too — "Euler1" becomes "Euler_i" and "Heun" becomes "Heun_i". See the method-name table under `<Engine>` / `<Sundials>` → `<Integrator Method="...">`.
Do not reach for kSimMgrKeyIntegrator in a setup hook. The integrator is built from the finished model, so it is registered only after every SimObject's Init(), FinalSetup() and InitialConditionSetup() have run; the pointer is null in all three and dereferencing it crashes. IFhIntegrator (now public, <fhsim/engine/IFhIntegrator.h>) is for reading the running integrator — current time, step size, diagnostics — from OdeFcn/AcceptedStep onwards.
Migration checklist:
OdeFcn overrides updated to new signature (const, no isMajorTimeStep)AcceptedStep(...)ICommonComputation callbacks updated to const"IntegratorOptions" lookups replaced by fhsim::kSimMgrKeyIntegratorSetupIf your project embeds the full simulation engine (constructing FhSim objects directly), you still link FhSim::fhsim:
The FhSim::fhsim target now depends on FhSim::simobject, so you get everything. Additional steps for engine embedders:
| Old class name | New class name |
|---|---|
CFhSim | FhSim |
CFhRtSim | *(no replacement class — see CFhRtSim is gone: real time is now a pacing mode)* |
CFhSimLicenceManager | FhSimLicenceManager |
CFhIntegrator | CFhIntegrator *(unchanged — engine-internal)* |
Old #include | New #include |
|---|---|
#include "CFhSim.h" | #include <fhsim/FhSim.h> |
#include "CFhRtSim.h" | *(remove — see CFhRtSim is gone: real time is now a pacing mode)* |
There is no real-time class in v3, and no header to include. A v2 project that constructed a CFhRtSim to get a wall-clock-paced run does not get a renamed class — real time became a property of the run instead.
Two things replaced it:
simulationSpeed attribute on the <SIMULATION> element (1.0 = real time, 2.0 = twice real time, 0 = as fast as possible). This is the only route for a headless FhSim run. On the command line, FhVis accepts -s / --simulation-speed and FhSimUI accepts --rtf / --rt; FhSim has no pacing flag. See Real-time pacing.fhsim::RealTimeStateProvider (src/engine/io/RealTimeStateProvider.h) wraps any IStateProvider and sleeps the remaining wall time after each step. If the model cannot keep up it warns and does not sleep — the same behaviour CFhRtSim::Simulate() had. Build it the way SimulationManagerFactory does and hand it to a fhsim::SimulationManager.Note that this is a behavioural move as well as a rename: pacing is applied around the step loop, so it composes with any provider (live integration, <Replay>, <Network>) instead of being a separate simulator subclass.
fhsim::SimulationManager (src/engine/io/SimulationManager.h) is now the one owner of the simulation lifecycle, and the loop methods that duplicated it on the engine facade are gone. FhSim::Step() remains: it advances exactly one integrator step.
| Removed | Replacement |
|---|---|
FhSim::Integrate() | Build a SimulationManager over an IntegratorStateProvider and call Run() (see src/apps/fhExe/main.cpp), or loop FhSim::Step() while it returns simRes_Success. |
FhSim::StepToTFinal() | Same as above. |
FhSim::GetOutput(XOut, TOut) | Register an observer (FileOutputObserver, or fhsim::test::CapturingObserver in tests) — the integrator no longer accumulates a state history. |
FhSim::GetNumOutput() | Same as above. |
FhSim::PrintStateValues() | The <Diagnostics><Settings><LogStates/></Settings></Diagnostics> diagnostic. |
FhVisSDL::IntegrateVis() | SimulationManager::Run() with a visualization observer registered (see src/apps/fhSimUI/FhSimUIMain.cpp). |
IFhIntegrator lost the matching virtuals — StepToTFinal(), GetOutputCopy(), GetNumOutput() and PrintStateValues(). Custom integrators must drop those overrides; Step(), the time-span accessors, the state accessors, LogConfiguration() and GetDiagnostics() are unchanged.
If your application uses the FhSim visualization layer (SDL/Ogre rendering), link the new FhSim::fhsim_vis target:
The <INTEGRATION> XML element has been renamed to <SIMULATION>. The old name is still accepted with a deprecation warning but will be removed in a future release.
The <Engine> and <Sundials> child elements inside <SIMULATION> are removed and replaced by a single unified <Integrator Method="..."> element. <Replay> and <Network> are unchanged.
Old (<Engine IntegratorMethod="N">) | New (<Integrator Method="...">) |
|---|---|
IntegratorMethod="0" (Euler1) | Method="Euler_i" |
IntegratorMethod="1" (StableSolver) | Method="StableSolver_i" |
IntegratorMethod="2" (RKCK45) | Method="RK45_i" |
IntegratorMethod="3" (RKDOPRI54) | Method="DOPRI54_i" |
IntegratorMethod="4" (Heun) | Method="Heun_i" |
IntegratorMethod="5" (RKF45) | Method="RKF45_i" |
IntegratorMethod="6" (BS23) | Method="BS23_i" |
IntegratorMethod="12" (Euler1imp) | Method="BackwardEuler_i" |
Sundials BDF (cvode_method="BDF") | Method="BDF" |
Sundials Adams (cvode_method="Adams") | Method="Adams" |
| ARKode DIRK | Method="DIRK" |
| ARKode ERK | Method="ERK" |
**
RKCK45changed coefficients.** In fhsim 2,RKCK45(IntegratorMethod="2") ran the Runge-Kutta-Fehlberg coefficients, despite its name, and propagated the 4th-order solution. In fhsim 3,RK45_iruns the Cash-Karp coefficients and propagates the 5th-order solution. Results change at the level of the local error tolerance. In non-smooth models (contacts, impacts) whereStepMaxis longer than the event, they can change much more. To reproduce fhsim 2 results, useMethod="RKF45_i", or set aStepMaxthat resolves the event.
Legacy Engine method names (RKCK45, Euler1imp, Euler1) are accepted as aliases (RKCK45 maps to RK45_i, Cash-Karp) with a deprecation warning. Use the canonical _i-suffixed names for Engine methods and plain names for Sundials methods.
Simulation start and end times move to a <Timing> sibling element:
Step size and tolerance settings move to a <StepControl> child of <Integrator>:
Old attribute (on <Engine> or <Sundials>) | New (<StepControl> attribute) |
|---|---|
dt / stepsize / Stepsize / Dt | Step |
abstol / AbsTol | AbsTol |
reltol / RelTol | RelTol |
HMax | StepMax |
HMin | StepMin |
| (Sundials) initial step | Step0 |
| (Sundials) max steps | MaxSteps |
v2 <Engine> attribute | v3 |
|---|---|
NumCores | <Integrator NumCores>. It takes "perSimObject", "perCpuCore" or a whole number of 1 or more. |
InitialStatesFile, FinalStatesFile | Stay on <Integrator> with the same names. |
TOutput | <OBSERVERS><FileOutput TOutput="..."/></OBSERVERS>. The syntax changed, see below. <Integrator TOutput> is an error. |
FileOutput | <OBSERVERS><FileOutput Select="..."/></OBSERVERS>. The v2 value was the selection of signals (for example objects:ports), not a file name. The file name is outputFile (default modeloutput.csv) or -o on the command line. |
LogStates | No v3 attribute. <Integrator> ignores it with a warning ("Unknown `<Integrator>` attribute ... ignored"). If you used it to see state values, write them to the result file with <FileOutput Select="objects:states"/>, or enable the LogStates diagnostic (see the diagnostics cookbook). |
UseRSSNormInsteadOfInfNorm | No v3 replacement. <Integrator> ignores it with a warning. No XML setting selects the root-sum-of-squares error norm, so the step-size controller always uses the maximum norm. A model that relied on the RSS norm may take other steps. Check its results, and adjust AbsTol and RelTol if needed. |
The output schedule. v2 TOutput was "TStart, schedule, TEnd", for example "0, 0:0.2:100, 100". v3 <FileOutput TOutput> reads every plain number in a comma list as an output time, so a pasted v2 string gives extra rows (at TStart and TEnd). Drop the first and the last number. TStart and TEnd now live in <Timing>:
An empty field takes the simulation start or end, so ":0.2:" also means every 0.2 s from TStart to TEnd. See Output scheduling (TOutput) for all forms.
<OBSERVERS> block, only the listed observers are created. FhVis opens a 3D view without a block, but with one it needs <Visualization/> in the list too.**Select names one object at a time.** A signal can be named only for a single object: object.<name>:state.<Signal> works, but objects:state.<Signal> and objects.<Type>:state.<Signal> fail with "a signal can only be named for a single
object". Use objects:states for all states, or list the objects with union(...). See OBSERVERS section.
| Old attribute | New element |
|---|---|
lin_type="SPGMR" | <LinearSolver Type="SPGMR"/> |
jac_type="dense" / jtype="dense" | <Jacobian Type="dense"/> |
<SundialsExtra linear_solver="component"/> (early v3) | <LinearSolver Type="SPGMR" Preconditioner="component"/> |
<SundialsExtra jacobian_policy="hybrid"/> (early v3) | <Jacobian Policy="hybrid"/> |
<LinearSolver Type="component" GsSweeps="N" GsSymmetric="true"/> (early v3) | <LinearSolver Type="component"/> — the two attributes tuned a fallback no model could reach and are now rejected |
The two <SundialsExtra> pseudo-keys selected model-side settings rather than SUNDIALS API calls; they are now rejected with an error that names the replacement attribute.
For advanced Sundials settings not covered by the schema, add a <SundialsExtra> child element with arbitrary attribute-value pairs that are forwarded to the Sundials API:
Before (v2 Engine):
After (v3):
Before (v2 Sundials BDF):
After (v3):
FhSim 3.0 introduces an observer/provider pipeline. Observers can be configured via a new optional <OBSERVERS> XML section:
See OBSERVERS section for full documentation.
| Condition | Behaviour |
|---|---|
Only <SIMULATION> present | ✅ Preferred — no warning |
Only <INTEGRATION> present | ⚠️ Accepted with deprecation warning |
Both <INTEGRATION> and <SIMULATION> present | ❌ Fatal error |
| Neither present | ❌ Fatal error |
<Integrator Method="..."> missing for Engine/Sundials simulations | ❌ Fatal error |
Unknown Method value | ❌ Fatal error |
Legacy method alias (RKCK45, Euler1imp, Euler1) | ⚠️ Accepted with deprecation warning |
Additional strict validation:
<Engine> and <Sundials> inside <SIMULATION> are rejected with a message pointing to <Integrator Method="...">; they are no longer parsed into anything.<Integrator MaxOrder> is rejected: the attribute lives on <StepControl MaxOrder>.<Integrator TOutput> is rejected: the output schedule lives on <OBSERVERS><FileOutput TOutput="..."/></OBSERVERS>. Without an <OBSERVERS> block the -o output file receives every step.<SIMULATION> fail fast.<OBSERVERS> fail fast.FMU note:
<SIMULATION> + <Timing> + <Integrator> form. FhFmuExport reads the exported <DefaultExperiment> — startTime, stopTime, tolerance and stepSize — from that form only: startTime/stopTime come from <Timing TStart TEnd>, and stepSize/tolerance from the Step and RelTol attributes of <Integrator><StepControl>. A model still written in the legacy form (<INTEGRATION>, or <SIMULATION> with <Engine StepSize> / <Sundials dt>) exports an empty <DefaultExperiment> element, so the importing co-simulation master is given no stop time and no suggested communication step size and has to be told both by hand. The port mapping itself is not affected — FmuPortMappingReader does read the legacy attributes, which is why the export succeeds and only the advertised experiment defaults go missing. If you hit migration issues in FMU export, contact the FhSim team.Most SimObject plugins use the InputReader / XmlDoc abstraction layer for XML access. The interface of these classes is unchanged — you do not need to modify your XML-reading code even though the underlying implementation switched from TinyXML to libxml2.
If your SimObject used TinyXML API directly (e.g., TiXmlElement*, TiXmlDocument), you must switch to the libxml2 API or (preferably) use the InputReader / XmlDoc wrappers instead.
The libxml2 API uses C-style allocation — always free strings returned by xmlGetProp / xmlNodeListGetString with xmlFree().
| TinyXML (v2) | libxml2 (v3) |
|---|---|
#include <tinyxml.h> | #include <libxml/parser.h> + #include <libxml/tree.h> |
TiXmlDocument doc(path); doc.LoadFile() | xmlDocPtr doc = xmlReadFile(path, nullptr, XML_PARSE_NONET); |
doc.ErrorDesc() | No direct equivalent; libxml2 writes to stderr by default |
doc.RootElement() | xmlDocGetRootElement(doc) |
elem->Value() | reinterpret_cast<const char*>(node->name) |
elem->Attribute("x") | xmlChar* v = xmlGetProp(node, BAD_CAST "x"); xmlFree(v); |
elem->FirstChildElement("tag") | Iterate node->children, skip node->type != XML_ELEMENT_NODE |
child->NextSiblingElement() | Iterate node->next, skip non-element nodes |
attr->Name() | reinterpret_cast<const char*>(attr->name) |
attr->Value() | xmlChar* v = xmlNodeListGetString(doc, attr->children, 1); xmlFree(v); |
| (no explicit free needed) | xmlFreeDoc(doc) after all nodes are done |
<fhsim/XmlDoc.h> wraps libxml2 in a small C++ API (namespace fhsim::xml). It owns the memory for you: there is no xmlFree, and no node to free. An XmlNode is a non-owning handle that is valid while its XmlDoc lives.
| TinyXML (v2) | fhsim::xml (v3) |
|---|---|
TiXmlDocument doc(path); doc.LoadFile() | auto doc = XmlDoc::FromFile(path, &error); then if (!doc) for failure |
doc.RootElement() | doc.Root() |
elem->Value() | node.Name() |
elem->Attribute("x") | node.Attribute("x") (empty string if missing) or node.OptAttribute("x") (std::optional) |
elem->FirstChildElement("tag") | node.FirstChildElement("tag") |
child->NextSiblingElement() | child.NextSiblingElement() |
a for loop over the children | for (auto child : node.ChildElements("tag")) |
Also remove tinyxml::tinyxml from any target_link_libraries() call in your CMakeLists.txt — the migration script (--only cmake) does this automatically.
| Old (v2) | New (v3) | Impact on consumers |
|---|---|---|
tinyxml/2.6.2 | libxml2/[~2.15] ([~2.13] up to 3.3.x) | Transitive; no action unless you used TinyXML API directly |
sundials/5.4.0 | sundials/7.5.0 | Transitive; only affects engine embedders using Sundials API directly |
| — | eigen/[~5.0.0] | New transitive dependency on simobject target |
| — | sdl/2.32.10 | Only with with_visualization=True |
| — | ImGui, built into ogre3d's OgreOverlay | Only with with_visualization=True. Up to 3.3.x a separate imgui/[<1.92] requirement; do not link imgui::imgui from 3.4.0 |
| — | zeromq/4.3.5 | Internal to fhsim; not visible to consumers |
| — | miniz/[~3.0.2] | Internal to fhsim; not visible to consumers |
A library that uses fhsim_environment moves to its 3.x line at the same time as fhsim. Two things change in the consumer (see the 3.0.0 entry of the fhsim_environment CHANGELOG):
include/fhsim_environment/, and the recipe's includedirs is plain include. Include them as #include <fhsim_environment/EnvironmentProvider.h>, not by bare file name.transitive_headers=True, so the libraries that use yours get the include path too (fhsim_base does, in its conanfile.py):In CMake, link the target fhsim_environment::environment. fhsim_environment 3 itself requires fhsim/[>=3.4.0 <4], so its consumers move to fhsim 3.4 or newer.
Ensure your Conan profile specifies:
An automated migration script is provided at scripts/migrate_to_v3.py. It performs header renames, class renames, CMake target updates, and XML element renames across your project.
.cpp, .h, .hpp):#include directives (e.g., "CPrintDuringExec.h" → "PrintDuringExec.h")CPrintDuringExec → PrintDuringExec)CMakeLists.txt, *.cmake):FhSim::fhsim with FhSim::simobject (SimObject target migration)FhSim::fhsim-testtools and FhSim::fhsim_testtools with FhSim::fhsim_test.xml):<INTEGRATION> / </INTEGRATION> with <SIMULATION> / </SIMULATION><Engine .../> or <Sundials .../> to <Timing/> and <Integrator> (see XML input file changes).TOutput, without TStart and TEnd) and the v2 FileOutput selection as <OBSERVERS><FileOutput TOutput=... Select=.../></OBSERVERS> after </SIMULATION>. If the file already has an <OBSERVERS> block, it adds a TODO comment with the <FileOutput> instead.LogStates and UseRSSNormInsteadOfInfNorm: they have no v3 attribute and are not copied to <Integrator>.conanfile.py, conanfile.txt):The following changes cannot be automated safely and must be done by hand after running the script:
string → std::string in headers** — the regex for word-boundary replacement would produce too many false positives. See Step 5b: Update headers that use unqualified string above.<Engine> / <Sundials> conversion — the script converts self-closing elements only, so an <Engine> or <Sundials> element with a closing tag stays as it was. Attributes the script does not know are not copied. Check each converted file against the mapping tables and examples in XML input file changes, and read the TODO comments the script leaves.GetSharedResource("CFhCamera") string literal** — string literal content is not renamed by the class-rename pass, and GetSharedResource now takes the type as a template argument: creator->GetSharedResource<FhCamera>(fhsim::kSimMgrKeyFhCamera). Search manually: FhSim::fhsim_vis CMake link guard** — the script does not add new target_link_libraries calls. See Step 7: Link FhSim::fhsim_vis and update visualization includes (if applicable).FhSim::fhsim → FhSim::simobject) handles both the mixed-case form (FhSim::fhsim) and the all-lowercase Conan CMakeDeps alias form (fhsim::fhsim). If you embed the engine rather than authoring a plugin, skip the CMake pass or restore FhSim::fhsim linkage manually where needed.--apply..git/, build/, or node_modules/ directories.