diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6e66c6a..901f928 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -17,12 +17,19 @@ jobs: with: submodules: recursive - - name: Generate Reference-FMUs Makefile - run: cmake -S Reference-FMUs -B bin + - name: Generate Reference-FMUs Makefile (FMI 2.0) + run: cmake -S Reference-FMUs -D FMI_VERSION=2 -B bin2 working-directory: FMU - - name: Build Reference-FMUs + - name: Build Reference-FMUs (FMI 2.0) run: make - working-directory: FMU/bin + working-directory: FMU/bin2 + + - name: Generate Reference-FMUs Makefile (FMI 3.0) + run: cmake -S Reference-FMUs -D FMI_VERSION=3 -B bin3 + working-directory: FMU + - name: Build Reference-FMUs (FMI 3.0) + run: make + working-directory: FMU/bin3 - name: Setup .NET Core uses: actions/setup-dotnet@v3 diff --git a/.gitignore b/.gitignore index df56373..69dab83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ obj -bin +bin* .vscode .ionide *.nupkg diff --git a/BouncingBall.gif b/BouncingBall.gif deleted file mode 100644 index 6ed48ef..0000000 Binary files a/BouncingBall.gif and /dev/null differ diff --git a/BouncingBall2.gif b/BouncingBall2.gif new file mode 100644 index 0000000..68e0915 Binary files /dev/null and b/BouncingBall2.gif differ diff --git a/BouncingBall3.gif b/BouncingBall3.gif new file mode 100644 index 0000000..68e0915 Binary files /dev/null and b/BouncingBall3.gif differ diff --git a/FMU/Reference-FMUs b/FMU/Reference-FMUs index e4ec2c4..83edd92 160000 --- a/FMU/Reference-FMUs +++ b/FMU/Reference-FMUs @@ -1 +1 @@ -Subproject commit e4ec2c4776ee4def4f4ca8d0422f18da1146275f +Subproject commit 83edd926179aa8b42d8494db6610c2528db351bf diff --git a/FMU/build-reference-FMUs.sh b/FMU/build-reference-FMUs.sh index 2e8e04d..05244e4 100755 --- a/FMU/build-reference-FMUs.sh +++ b/FMU/build-reference-FMUs.sh @@ -1,4 +1,4 @@ snap install cmake -cmake -S Reference-FMUs -B bin -cd bin -make +rm -rf bin* +cmake -S Reference-FMUs -D FMI_VERSION=2 -B bin2 && pushd bin2 && make && popd +cmake -S Reference-FMUs -D FMI_VERSION=3 -B bin3 && pushd bin3 && make && popd diff --git a/Femyou.sln b/Femyou.sln index 8ca1708..daf7f98 100644 --- a/Femyou.sln +++ b/Femyou.sln @@ -17,6 +17,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "misc", "misc", "{9D2D98F1-B .gitignore = .gitignore README.md = README.md logo.svg = logo.svg + FMU\build-reference-FMUs.sh = FMU\build-reference-FMUs.sh EndProjectSection EndProject Global diff --git a/README.md b/README.md index 4b1172b..0cffa89 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,10 @@ loading and running [fmi-standard](https://fmi-standard.org/) FMUs in .NET ## Features -This is a limited implementation of the fmi-standard. +This is a limited implementation of the fmi-standards. Available features : -* Load FMU model +* Load FMU model encoded in either FMI 2.0 or 3.0 standard * Get model variables * Create co-simulation instance * Read and write variable values from instance @@ -42,8 +42,10 @@ while (h > 0 || Math.Abs(v) > 0) ## Demos -The [Bouncing Ball](https://github.com/modelica/Reference-FMUs/tree/master/BouncingBall) reference FMU visualized with GIF animation. +These GIFs were created with +- the included demo program +- the [Bouncing Ball](https://github.com/modelica/Reference-FMUs/tree/master/BouncingBall) reference FMU compiled in FMI 2.0 and 3.0 standards -This GIF was created with the included demo program. - -![BouncingBall](BouncingBall.gif?raw=true) +| FMI 2.0 | FMI 3.0 | +|---------|---------| +| ![](BouncingBall2.gif?raw=true) | ![](BouncingBall3.gif?raw=true) | diff --git a/demos/BouncingBallGif/Program.cs b/demos/BouncingBallGif/Program.cs index e4cbf32..ecf1231 100644 --- a/demos/BouncingBallGif/Program.cs +++ b/demos/BouncingBallGif/Program.cs @@ -58,7 +58,7 @@ static void Main() new Uri(System.Reflection.Assembly.GetExecutingAssembly().Location).AbsolutePath, nameof(Femyou) ), "FMU", - "bin", + "bin3", "dist" ); diff --git a/src/Femyou.csproj b/src/Femyou.csproj index a77e6e4..fba7554 100644 --- a/src/Femyou.csproj +++ b/src/Femyou.csproj @@ -1,7 +1,7 @@ - Loading and running fmi-standard FMUs in dotnet core. + Loading and running fmi-standard FMUs in .NET Femyou oaz Femyou @@ -12,7 +12,7 @@ Copyright © Olivier Azeau netstandard2.1 - 0.2.0 + 0.3.0 diff --git a/src/Internal/Callbacks.cs b/src/Internal/Callbacks.cs index 8ee2583..7164064 100644 --- a/src/Internal/Callbacks.cs +++ b/src/Internal/Callbacks.cs @@ -1,48 +1,19 @@ using System; -using System.Runtime.InteropServices; namespace Femyou.Internal { - class Callbacks : IDisposable + public abstract class Callbacks : IDisposable { public Callbacks(Instance instance, ICallbacks cb) { - _instance = instance; - _cb = cb; - _handle = GCHandle.Alloc(this); - _functions = new FMI2.fmi2CallbackFunctions - { - logger = LoggerCallback, - allocateMemory = Marshalling.AllocateMemory, - freeMemory = Marshalling.FreeMemory, - stepFinished = StepFinishedCallback, - componentEnvironment = GCHandle.ToIntPtr(_handle) - }; - Structure = Marshalling.AllocateMemory(1, (ulong)Marshal.SizeOf(_functions)); - Marshal.StructureToPtr(_functions, Structure, false); + Instance = instance; + Cb = cb; } - private readonly Instance _instance; - private readonly ICallbacks _cb; - public readonly IntPtr Structure; - private GCHandle _handle; - // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable - private readonly FMI2.fmi2CallbackFunctions _functions; - - public void Dispose() - { - Marshalling.FreeMemory(Structure); - _handle.Free(); - } + public readonly Instance Instance; + public readonly ICallbacks Cb; + public abstract IntPtr Custom { get; } - private static void LoggerCallback(IntPtr componentEnvironment, string instanceName, int status, string category, string message) - { - var self = (Callbacks) GCHandle.FromIntPtr(componentEnvironment).Target; - self._cb?.Logger(self._instance, (Status)status, category, message); - } - - private static void StepFinishedCallback(IntPtr componentEnvironment, int status) - { - } + public abstract void Dispose(); } } \ No newline at end of file diff --git a/src/Internal/Callbacks2.cs b/src/Internal/Callbacks2.cs new file mode 100644 index 0000000..06c6c52 --- /dev/null +++ b/src/Internal/Callbacks2.cs @@ -0,0 +1,49 @@ +using System; +using System.Runtime.InteropServices; +using Femyou.Interop; + +namespace Femyou.Internal +{ + class Callbacks2 : Callbacks + { + public Callbacks2(Instance instance, ICallbacks cb) + : base(instance, cb) + { + _handle = GCHandle.Alloc(this); + _functions = new FMI2.fmi2CallbackFunctions + { + logger = LoggerCallback, + allocateMemory = Marshalling.AllocateMemory, + freeMemory = Marshalling.FreeMemory, + stepFinished = StepFinishedCallback, + componentEnvironment = GCHandle.ToIntPtr(_handle) + }; + _structure = Marshalling.AllocateMemory(1, (ulong)Marshal.SizeOf(_functions)); + Marshal.StructureToPtr(_functions, _structure, false); + } + + private readonly IntPtr _structure; + private GCHandle _handle; + // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable + private readonly FMI2.fmi2CallbackFunctions _functions; + + public override IntPtr Custom => _structure; + + public override void Dispose() + { + Marshalling.FreeMemory(_structure); + _handle.Free(); + } + + private static void LoggerCallback(IntPtr componentEnvironment, string instanceName, int status, string category, string message) + { + var self = (Callbacks) GCHandle.FromIntPtr(componentEnvironment).Target; + self.Cb?.Logger(self.Instance, (Status)status, category, message); + } + + private static void StepFinishedCallback(IntPtr componentEnvironment, int status) + { + } + } + +} \ No newline at end of file diff --git a/src/Internal/Callbacks3.cs b/src/Internal/Callbacks3.cs new file mode 100644 index 0000000..0cbee65 --- /dev/null +++ b/src/Internal/Callbacks3.cs @@ -0,0 +1,36 @@ +using System; +using System.Runtime.InteropServices; +using Femyou.Interop; + +namespace Femyou.Internal +{ + class Callbacks3 : Callbacks + { + public Callbacks3(Instance instance, ICallbacks cb) + : base(instance, cb) + { + _handle = GCHandle.Alloc(this); + Custom = GCHandle.ToIntPtr(_handle); + LogMessageDelegate = LoggerCallback; + } + + private GCHandle _handle; + public readonly FMI3.fmi3CallbackLogMessage LogMessageDelegate; + + + private static void LoggerCallback( + IntPtr componentEnvironment, string instanceName, + int status, string category, string message) + { + var self = (Callbacks) GCHandle.FromIntPtr(componentEnvironment).Target; + self.Cb?.Logger(self.Instance, (Status)status, category, message); + } + + public override IntPtr Custom { get; } + + public override void Dispose() + { + _handle.Free(); + } + } +} \ No newline at end of file diff --git a/src/Internal/Instance.cs b/src/Internal/Instance.cs index cc0d1af..253112f 100644 --- a/src/Internal/Instance.cs +++ b/src/Internal/Instance.cs @@ -1,131 +1,74 @@ using System; using System.Collections.Generic; -using System.Linq; namespace Femyou.Internal { - class Instance : IInstance + public class Instance : IInstance { - public Instance(string name, ModelImpl model, Library library, FMI2.fmi2Type instanceType, ICallbacks cb) + public Instance(string name, ModelImpl model, Library library, ICallbacks cb) { Name = name; - this._library = library; - _callbacks = new Callbacks(this,cb); - _handle = library.fmi2Instantiate( + _library = library; + _callbacks = library.CreateCallbacks(this, cb); + + _handle = library.Instantiate( name, - instanceType, model.Guid, model.TmpFolder, - _callbacks.Structure, - FMI2.fmi2Boolean.fmi2False, - FMI2.fmi2Boolean.fmi2False + _callbacks ); if (_handle == IntPtr.Zero) - throw new FmuException("Cannot instanciate model"); + throw new FmuException("Cannot instantiate model"); } + public string Name { get; } public double CurrentTime { get; private set; } public void StartTime(double time) { CurrentTime = time; - _library.fmi2SetupExperiment( + _library.Setup( _handle, - FMI2.fmi2Boolean.fmi2False, - 0.0, - CurrentTime, - FMI2.fmi2Boolean.fmi2False, - 0.0 - ); - _library.fmi2EnterInitializationMode(_handle); - _library.fmi2ExitInitializationMode(_handle); + CurrentTime + ); _started = true; } + public void AdvanceTime(double step) { - if(step == 0.0) + if (step == 0.0) return; - _library.fmi2DoStep( + _library.Step( _handle, CurrentTime, - step, - FMI2.fmi2Boolean.fmi2True - ); + step + ); CurrentTime += step; } + public IEnumerable ReadReal(IEnumerable variables) => - Read( - variables, - l => new double[l], - (a, b, c, d) => _library.fmi2GetReal(a, b, c, d) - ); + _library.ReadReal(_handle, variables); public IEnumerable ReadInteger(IEnumerable variables) => - Read( - variables, - l => new int[l], - (a, b, c, d) => _library.fmi2GetInteger(a, b, c, d) - ); + _library.ReadInteger(_handle, variables); public IEnumerable ReadBoolean(IEnumerable variables) => - Read( - variables, - l => new FMI2.fmi2Boolean[l], - (a, b, c, d) => _library.fmi2GetBoolean(a, b, c, d) - ).Select(r => r == FMI2.fmi2Boolean.fmi2True); + _library.ReadBoolean(_handle, variables); public IEnumerable ReadString(IEnumerable variables) => - Read( - variables, - Marshalling.CreateArray, - (a, b, c, d) => _library.fmi2GetString(a, b, c, d) - ).Select(Marshalling.GetString); + _library.ReadString(_handle, variables); - private T[] Read( - IEnumerable variables, - Func createArray, - Func call - ) - { - var inputArray = variables as IVariable[] ?? variables.ToArray(); - var valueReferences = inputArray.Cast().Select(vs => vs.ValueReference).ToArray(); - var values = createArray(inputArray.Length); - var status = call(_handle, valueReferences, (ulong)valueReferences.Length, values); - if (status != 0) - throw new FmuException("Failed to read"); - return values; - } + public void WriteReal(IEnumerable<(IVariable, double)> variables) => + _library.WriteReal(_handle, variables); - public void WriteReal(IEnumerable<(IVariable, double)> variables) => Write( - variables, - (a, b, c, d) => _library.fmi2SetReal(a, b, c, d) - ); - public void WriteInteger(IEnumerable<(IVariable, int)> variables) => Write( - variables, - (a, b, c, d) => _library.fmi2SetInteger(a, b, c, d) - ); - public void WriteBoolean(IEnumerable<(IVariable, bool)> variables) => Write( - variables.Select(v => (v.Item1, v.Item2 ? FMI2.fmi2Boolean.fmi2True : FMI2.fmi2Boolean.fmi2False)), - (a, b, c, d) => _library.fmi2SetBoolean(a, b, c, d) - ); - public void WriteString(IEnumerable<(IVariable, string)> variables) => Write( - variables, - (a, b, c, d) => _library.fmi2SetString(a, b, c, d) - ); + public void WriteInteger(IEnumerable<(IVariable, int)> variables) => + _library.WriteInteger(_handle, variables); - private void Write(IEnumerable<(IVariable, T)> variables, Func call) - { - var valueTuples = variables as (IVariable, T)[] ?? variables.ToArray(); - var valueReferences = valueTuples - .Select(vs => vs.Item1) - .Cast() - .Select(vs => vs.ValueReference) - .ToArray(); - var values = valueTuples.Select(vs => vs.Item2).ToArray(); - var status = call(_handle, valueReferences, (ulong)valueReferences.Length, values); - if (status != 0) - throw new FmuException("Failed to write"); - } + public void WriteBoolean(IEnumerable<(IVariable, bool)> variables) => + _library.WriteBoolean(_handle, variables); + + public void WriteString(IEnumerable<(IVariable, string)> variables) => + _library.WriteString(_handle, variables); private readonly Library _library; private readonly IntPtr _handle; @@ -134,9 +77,7 @@ private void Write(IEnumerable<(IVariable, T)> variables, Func(nameof(fmi2Instantiate)); - fmi2FreeInstance = _fmuLibrary.LoadFunction(nameof(fmi2FreeInstance)); - fmi2SetupExperiment = _fmuLibrary.LoadFunction(nameof(fmi2SetupExperiment)); - fmi2EnterInitializationMode = _fmuLibrary.LoadFunction(nameof(fmi2EnterInitializationMode)); - fmi2ExitInitializationMode = _fmuLibrary.LoadFunction(nameof(fmi2ExitInitializationMode)); - fmi2Terminate = _fmuLibrary.LoadFunction(nameof(fmi2Terminate)); - fmi2Reset = _fmuLibrary.LoadFunction(nameof(fmi2Reset)); - - fmi2GetReal = _fmuLibrary.LoadFunction(nameof(fmi2GetReal)); - fmi2GetInteger = _fmuLibrary.LoadFunction(nameof(fmi2GetInteger)); - fmi2GetBoolean = _fmuLibrary.LoadFunction(nameof(fmi2GetBoolean)); - fmi2GetString = _fmuLibrary.LoadFunction(nameof(fmi2GetString)); - fmi2SetReal = _fmuLibrary.LoadFunction(nameof(fmi2SetReal)); - fmi2SetInteger = _fmuLibrary.LoadFunction(nameof(fmi2SetInteger)); - fmi2SetBoolean = _fmuLibrary.LoadFunction(nameof(fmi2SetBoolean)); - fmi2SetString = _fmuLibrary.LoadFunction(nameof(fmi2SetString)); - - fmi2DoStep = _fmuLibrary.LoadFunction(nameof(fmi2DoStep)); + FmuLibrary = new NativeLibrary(path); } - // ReSharper disable InconsistentNaming -- must use fmi standard names to load function in library - public readonly FMI2.fmi2InstantiateTYPE fmi2Instantiate; - public readonly FMI2.fmi2FreeInstanceTYPE fmi2FreeInstance; - public readonly FMI2.fmi2SetupExperimentTYPE fmi2SetupExperiment; - public readonly FMI2.fmi2EnterInitializationModeTYPE fmi2EnterInitializationMode; - public readonly FMI2.fmi2ExitInitializationModeTYPE fmi2ExitInitializationMode; - public readonly FMI2.fmi2TerminateTYPE fmi2Terminate; - public readonly FMI2.fmi2ResetTYPE fmi2Reset; - public readonly FMI2.fmi2GetRealTYPE fmi2GetReal; - public readonly FMI2.fmi2GetIntegerTYPE fmi2GetInteger; - public readonly FMI2.fmi2GetBooleanTYPE fmi2GetBoolean; - public readonly FMI2.fmi2GetStringTYPE fmi2GetString; - public readonly FMI2.fmi2SetRealTYPE fmi2SetReal; - public readonly FMI2.fmi2SetIntegerTYPE fmi2SetInteger; - public readonly FMI2.fmi2SetBooleanTYPE fmi2SetBoolean; - public readonly FMI2.fmi2SetStringTYPE fmi2SetString; - public readonly FMI2.fmi2DoStepTYPE fmi2DoStep; - - private readonly NativeLibrary _fmuLibrary; + protected readonly NativeLibrary FmuLibrary; public void Dispose() { - _fmuLibrary.Dispose(); + FmuLibrary.Dispose(); } + public abstract Callbacks CreateCallbacks(Instance instance, ICallbacks cb); + + public abstract IntPtr Instantiate(string name, string modelGuid, string modelTmpFolder, Callbacks callbacks); + public abstract void Setup(IntPtr handle, double currentTime); + public abstract void Step(IntPtr handle, double currentTime, double step); + public abstract void Shutdown(IntPtr handle, bool started); + + public abstract IEnumerable ReadReal(IntPtr handle, IEnumerable variables); + public abstract IEnumerable ReadInteger(IntPtr handle, IEnumerable variables); + public abstract IEnumerable ReadBoolean(IntPtr handle, IEnumerable variables); + public abstract IEnumerable ReadString(IntPtr handle, IEnumerable variables); + public abstract void WriteReal(IntPtr handle, IEnumerable<(IVariable, double)> variables); + public abstract void WriteInteger(IntPtr handle, IEnumerable<(IVariable, int)> variables); + public abstract void WriteBoolean(IntPtr handle, IEnumerable<(IVariable, bool)> variables); + public abstract void WriteString(IntPtr handle, IEnumerable<(IVariable, string)> variables); + } -} \ No newline at end of file +} diff --git a/src/Internal/Library2.cs b/src/Internal/Library2.cs new file mode 100644 index 0000000..0d470fb --- /dev/null +++ b/src/Internal/Library2.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Femyou.Interop; + +namespace Femyou.Internal +{ + class Library2 : Library + { + public Library2(string path) : base(path) + { + fmi2Instantiate = FmuLibrary.LoadFunction(nameof(fmi2Instantiate)); + fmi2FreeInstance = FmuLibrary.LoadFunction(nameof(fmi2FreeInstance)); + fmi2SetupExperiment = FmuLibrary.LoadFunction(nameof(fmi2SetupExperiment)); + fmi2EnterInitializationMode = FmuLibrary.LoadFunction(nameof(fmi2EnterInitializationMode)); + fmi2ExitInitializationMode = FmuLibrary.LoadFunction(nameof(fmi2ExitInitializationMode)); + fmi2Terminate = FmuLibrary.LoadFunction(nameof(fmi2Terminate)); + fmi2DoStep = FmuLibrary.LoadFunction(nameof(fmi2DoStep)); + + fmi2GetReal = FmuLibrary.LoadFunction(nameof(fmi2GetReal)); + fmi2GetInteger = FmuLibrary.LoadFunction(nameof(fmi2GetInteger)); + fmi2GetBoolean = FmuLibrary.LoadFunction(nameof(fmi2GetBoolean)); + fmi2GetString = FmuLibrary.LoadFunction(nameof(fmi2GetString)); + fmi2SetReal = FmuLibrary.LoadFunction(nameof(fmi2SetReal)); + fmi2SetInteger = FmuLibrary.LoadFunction(nameof(fmi2SetInteger)); + fmi2SetBoolean = FmuLibrary.LoadFunction(nameof(fmi2SetBoolean)); + fmi2SetString = FmuLibrary.LoadFunction(nameof(fmi2SetString)); + } + + // ReSharper disable InconsistentNaming -- must use fmi standard names to load function in library + private readonly FMI2.fmi2InstantiateTYPE fmi2Instantiate; + private readonly FMI2.fmi2FreeInstanceTYPE fmi2FreeInstance; + private readonly FMI2.fmi2SetupExperimentTYPE fmi2SetupExperiment; + private readonly FMI2.fmi2EnterInitializationModeTYPE fmi2EnterInitializationMode; + private readonly FMI2.fmi2ExitInitializationModeTYPE fmi2ExitInitializationMode; + private readonly FMI2.fmi2TerminateTYPE fmi2Terminate; + private readonly FMI2.fmi2GetRealTYPE fmi2GetReal; + private readonly FMI2.fmi2GetIntegerTYPE fmi2GetInteger; + private readonly FMI2.fmi2GetBooleanTYPE fmi2GetBoolean; + private readonly FMI2.fmi2GetStringTYPE fmi2GetString; + private readonly FMI2.fmi2SetRealTYPE fmi2SetReal; + private readonly FMI2.fmi2SetIntegerTYPE fmi2SetInteger; + private readonly FMI2.fmi2SetBooleanTYPE fmi2SetBoolean; + private readonly FMI2.fmi2SetStringTYPE fmi2SetString; + private readonly FMI2.fmi2DoStepTYPE fmi2DoStep; + + public override Callbacks CreateCallbacks(Instance instance, ICallbacks cb) => + new Callbacks2(instance, cb); + + public override IntPtr Instantiate(string name, string guid, string tmpFolder, Callbacks callbacks) => + fmi2Instantiate( + name, + FMI2.fmi2Type.fmi2CoSimulation, + guid, + tmpFolder, + callbacks.Custom, + FMI2.fmi2Boolean.fmi2False, + FMI2.fmi2Boolean.fmi2False + ); + + public override void Setup(IntPtr handle, double currentTime) + { + fmi2SetupExperiment( + handle, + FMI2.fmi2Boolean.fmi2False, + 0.0, + currentTime, + FMI2.fmi2Boolean.fmi2False, + 0.0 + ); + fmi2EnterInitializationMode(handle); + fmi2ExitInitializationMode(handle); + } + + public override void Step(IntPtr handle, double currentTime, double step) => + fmi2DoStep( + handle, + currentTime, + step, + FMI2.fmi2Boolean.fmi2True + ); + + public override void Shutdown(IntPtr handle, bool started) + { + if (started) + fmi2Terminate(handle); + fmi2FreeInstance(handle); + } + + public override IEnumerable ReadReal(IntPtr handle, IEnumerable variables) => + Read( + handle, + variables, + l => new double[l], + (a, b, c, d) => fmi2GetReal(a, b, c, d) + ); + + public override IEnumerable ReadInteger(IntPtr handle, IEnumerable variables) => + Read( + handle, + variables, + l => new int[l], + (a, b, c, d) => fmi2GetInteger(a, b, c, d) + ); + + public override IEnumerable ReadBoolean(IntPtr handle, IEnumerable variables) => + Read( + handle, + variables, + l => new FMI2.fmi2Boolean[l], + (a, b, c, d) => fmi2GetBoolean(a, b, c, d) + ).Select(r => r == FMI2.fmi2Boolean.fmi2True); + + public override IEnumerable ReadString(IntPtr handle, IEnumerable variables) => + Read( + handle, + variables, + Marshalling.CreateArray, + (a, b, c, d) => fmi2GetString(a, b, c, d) + ).Select(Marshalling.GetString); + + private T[] Read( + IntPtr handle, + IEnumerable variables, + Func createArray, + Func call + ) + { + var inputArray = variables as IVariable[] ?? variables.ToArray(); + var valueReferences = inputArray.Cast().Select(vs => vs.ValueReference).ToArray(); + var values = createArray(inputArray.Length); + var status = call(handle, valueReferences, (ulong)valueReferences.Length, values); + if (status != 0) + throw new FmuException("Failed to read"); + return values; + } + + public override void WriteReal(IntPtr handle, IEnumerable<(IVariable, double)> variables) => Write( + handle, + variables, + (a, b, c, d) => fmi2SetReal(a, b, c, d) + ); + public override void WriteInteger(IntPtr handle, IEnumerable<(IVariable, int)> variables) => Write( + handle, + variables, + (a, b, c, d) => fmi2SetInteger(a, b, c, d) + ); + public override void WriteBoolean(IntPtr handle, IEnumerable<(IVariable, bool)> variables) => Write( + handle, + variables.Select(v => (v.Item1, v.Item2 ? FMI2.fmi2Boolean.fmi2True : FMI2.fmi2Boolean.fmi2False)), + (a, b, c, d) => fmi2SetBoolean(a, b, c, d) + ); + public override void WriteString(IntPtr handle, IEnumerable<(IVariable, string)> variables) => Write( + handle, + variables, + (a, b, c, d) => fmi2SetString(a, b, c, d) + ); + + private void Write( + IntPtr handle, + IEnumerable<(IVariable, T)> variables, + Func call + ) + { + var valueTuples = variables as (IVariable, T)[] ?? variables.ToArray(); + var valueReferences = valueTuples + .Select(vs => vs.Item1) + .Cast() + .Select(vs => vs.ValueReference) + .ToArray(); + var values = valueTuples.Select(vs => vs.Item2).ToArray(); + var status = call(handle, valueReferences, (ulong)valueReferences.Length, values); + if (status != 0) + throw new FmuException("Failed to write"); + } + + } +} \ No newline at end of file diff --git a/src/Internal/Library3.cs b/src/Internal/Library3.cs new file mode 100644 index 0000000..8884d6a --- /dev/null +++ b/src/Internal/Library3.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Femyou.Interop; + +namespace Femyou.Internal +{ + class Library3 : Library + { + public Library3(string path) : base(path) + { + fmi3InstantiateBasicCoSimulation = FmuLibrary.LoadFunction(nameof(fmi3InstantiateBasicCoSimulation)); + fmi3FreeInstance = FmuLibrary.LoadFunction(nameof(fmi3FreeInstance)); + fmi3EnterInitializationMode = FmuLibrary.LoadFunction(nameof(fmi3EnterInitializationMode)); + fmi3ExitInitializationMode = FmuLibrary.LoadFunction(nameof(fmi3ExitInitializationMode)); + fmi3EnterStepMode = FmuLibrary.LoadFunction(nameof(fmi3EnterStepMode)); + fmi3Terminate = FmuLibrary.LoadFunction(nameof(fmi3Terminate)); + fmi3DoStep = FmuLibrary.LoadFunction(nameof(fmi3DoStep)); + + fmi3GetFloat64 = FmuLibrary.LoadFunction(nameof(fmi3GetFloat64)); + fmi3GetInt32 = FmuLibrary.LoadFunction(nameof(fmi3GetInt32)); + fmi3GetBoolean = FmuLibrary.LoadFunction(nameof(fmi3GetBoolean)); + fmi3GetString = FmuLibrary.LoadFunction(nameof(fmi3GetString)); + fmi3SetFloat64 = FmuLibrary.LoadFunction(nameof(fmi3SetFloat64)); + fmi3SetInt32 = FmuLibrary.LoadFunction(nameof(fmi3SetInt32)); + fmi3SetBoolean = FmuLibrary.LoadFunction(nameof(fmi3SetBoolean)); + fmi3SetString = FmuLibrary.LoadFunction(nameof(fmi3SetString)); + } + + // ReSharper disable InconsistentNaming -- must use fmi standard names to load function in library + private readonly FMI3.fmi3InstantiateBasicCoSimulationTYPE fmi3InstantiateBasicCoSimulation; + private readonly FMI3.fmi3FreeInstanceTYPE fmi3FreeInstance; + private readonly FMI3.fmi3EnterInitializationModeTYPE fmi3EnterInitializationMode; + private readonly FMI3.fmi3ExitInitializationModeTYPE fmi3ExitInitializationMode; + private readonly FMI3.fmi3EnterStepModeTYPE fmi3EnterStepMode; + private readonly FMI3.fmi3TerminateTYPE fmi3Terminate; + private readonly FMI3.fmi3DoStepTYPE fmi3DoStep; + + private readonly FMI3.fmi3GetFloat64TYPE fmi3GetFloat64; + private readonly FMI3.fmi3GetInt32TYPE fmi3GetInt32; + private readonly FMI3.fmi3GetBooleanTYPE fmi3GetBoolean; + private readonly FMI3.fmi3GetStringTYPE fmi3GetString; + private readonly FMI3.fmi3SetFloat64TYPE fmi3SetFloat64; + private readonly FMI3.fmi3SetInt32TYPE fmi3SetInt32; + private readonly FMI3.fmi3SetBooleanTYPE fmi3SetBoolean; + private readonly FMI3.fmi3SetStringTYPE fmi3SetString; + + public override Callbacks CreateCallbacks(Instance instance, ICallbacks cb) => + new Callbacks3(instance, cb); + + public override IntPtr Instantiate(string name, string guid, string tmpFolder, Callbacks callbacks) + { + return fmi3InstantiateBasicCoSimulation( + name, + guid, + tmpFolder, + FMI3.fmi3Boolean.fmi3False, + FMI3.fmi3Boolean.fmi3True, + FMI3.fmi3Boolean.fmi3False, + FMI3.fmi3Boolean.fmi3False, + FMI3.fmi3Boolean.fmi3False, + callbacks.Custom, + ((Callbacks3)callbacks).LogMessageDelegate, + IntPtr.Zero + ); + } + + public override void Setup(IntPtr handle, double currentTime) + { + fmi3EnterInitializationMode( + handle, + FMI3.fmi3Boolean.fmi3False, + 0.0, + currentTime, + FMI3.fmi3Boolean.fmi3False, + 0.0 + ); + fmi3ExitInitializationMode(handle); + } + + public override void Step(IntPtr handle, double currentTime, double step) + { + fmi3DoStep( + handle, + currentTime, + step, + FMI3.fmi3Boolean.fmi3True, + IntPtr.Zero, + IntPtr.Zero, + IntPtr.Zero + ); + } + + public override void Shutdown(IntPtr handle, bool started) + { + if (started) + fmi3Terminate(handle); + fmi3FreeInstance(handle); + } + + public override IEnumerable ReadReal(IntPtr handle, IEnumerable variables) => + Read( + handle, + variables, + l => new double[l], + (a, b, c, d, e) => fmi3GetFloat64(a, b, c, d, e) + ); + + public override IEnumerable ReadInteger(IntPtr handle, IEnumerable variables) => + Read( + handle, + variables, + l => new int[l], + (a, b, c, d, e) => fmi3GetInt32(a, b, c, d, e) + ); + + public override IEnumerable ReadBoolean(IntPtr handle, IEnumerable variables) => + Read( + handle, + variables, + l => new FMI3.fmi3Boolean[l], + (a, b, c, d, e) => fmi3GetBoolean(a, b, c, d, e) + ).Select(r => r == FMI3.fmi3Boolean.fmi3True); + + public override IEnumerable ReadString(IntPtr handle, IEnumerable variables) => + Read( + handle, + variables, + Marshalling.CreateArray, + (a, b, c, d, e) => fmi3GetString(a, b, c, d, e) + ).Select(Marshalling.GetString); + + private T[] Read( + IntPtr handle, + IEnumerable variables, + Func createArray, + Func call + ) + { + var inputArray = variables as IVariable[] ?? variables.ToArray(); + var valueReferences = inputArray.Cast().Select(vs => vs.ValueReference).ToArray(); + var values = createArray(inputArray.Length); + var status = call(handle, valueReferences, (ulong)valueReferences.Length, values, (ulong)values.Length); + if (status != 0) + throw new FmuException("Failed to read"); + return values; + } + + public override void WriteReal(IntPtr handle, IEnumerable<(IVariable, double)> variables) => Write( + handle, + variables, + (a, b, c, d, e) => fmi3SetFloat64(a, b, c, d, e) + ); + public override void WriteInteger(IntPtr handle, IEnumerable<(IVariable, int)> variables) => Write( + handle, + variables, + (a, b, c, d, e) => fmi3SetInt32(a, b, c, d, e) + ); + public override void WriteBoolean(IntPtr handle, IEnumerable<(IVariable, bool)> variables) => Write( + handle, + variables.Select(v => (v.Item1, v.Item2 ? FMI3.fmi3Boolean.fmi3True : FMI3.fmi3Boolean.fmi3False)), + (a, b, c, d, e) => fmi3SetBoolean(a, b, c, d, e) + ); + public override void WriteString(IntPtr handle, IEnumerable<(IVariable, string)> variables) => Write( + handle, + variables, + (a, b, c, d, e) => fmi3SetString(a, b, c, d, e) + ); + + private void Write( + IntPtr handle, + IEnumerable<(IVariable, T)> variables, + Func call + ) + { + var valueTuples = variables as (IVariable, T)[] ?? variables.ToArray(); + var valueReferences = valueTuples + .Select(vs => vs.Item1) + .Cast() + .Select(vs => vs.ValueReference) + .ToArray(); + var values = valueTuples.Select(vs => vs.Item2).ToArray(); + var status = call(handle, valueReferences, (ulong)valueReferences.Length, values, (ulong)values.Length); + if (status != 0) + throw new FmuException("Failed to write"); + } + } +} \ No newline at end of file diff --git a/src/Internal/ModelImpl.cs b/src/Internal/ModelImpl.cs index 4df52d1..083bea1 100644 --- a/src/Internal/ModelImpl.cs +++ b/src/Internal/ModelImpl.cs @@ -7,10 +7,8 @@ namespace Femyou.Internal { - class ModelImpl : IModel + public class ModelImpl : IModel { - public readonly string TmpFolder; - public ModelImpl(string fmuPath) { try @@ -24,14 +22,17 @@ public ModelImpl(string fmuPath) !.Elements() .Select(sv => new Variable(sv) as IVariable) .ToDictionary(sv => sv.Name, sv => sv); + var fmiVersion = root!.Attribute("fmiVersion")?.Value; + ModelVersion = fmiVersion!.StartsWith("2.") ? (IModelVersion) new ModelVersion2() : new ModelVersion3(); var coSimulationId = root - !.Element("CoSimulation") + !.Element(ModelVersion.CoSimulationElementName) !.Attribute("modelIdentifier") !.Value; - _coSimulation = new Library(TmpFolder,coSimulationId); + var libPath = GetLibPath(ModelVersion, coSimulationId); + _library = ModelVersion.Load(libPath); Name = root!.Attribute("modelName")!.Value; - Description = root!.Attribute("description")!.Value; - Guid = root!.Attribute("guid")!.Value; + Description = root?.Attribute("description")?.Value; + Guid = root!.Attribute(ModelVersion.GuidAttributeName)!.Value; } catch (Exception e) { @@ -39,17 +40,31 @@ public ModelImpl(string fmuPath) } } + private string GetLibPath(IModelVersion version, string coSimulationId) => + Path.Combine( + TmpFolder, + version.RelativePath( + coSimulationId, + System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture, + Environment.OSVersion.Platform + ) + ); + + public readonly string TmpFolder; public string Name { get; } public string Description { get; } public string Guid { get; } + public IModelVersion ModelVersion { get; } public IReadOnlyDictionary Variables { get; } - public IInstance CreateCoSimulationInstance(string name, ICallbacks callbacks) => new Instance(name,this,_coSimulation,FMI2.fmi2Type.fmi2CoSimulation,callbacks); - private readonly Library _coSimulation; + public IInstance CreateCoSimulationInstance(string name, ICallbacks callbacks) => + new Instance(name, this, _library, callbacks); + + private readonly Library _library; public void Dispose() { - _coSimulation.Dispose(); + _library.Dispose(); Directory.Delete(TmpFolder,true); } } diff --git a/src/Internal/ModelVersion.cs b/src/Internal/ModelVersion.cs new file mode 100644 index 0000000..0ae5327 --- /dev/null +++ b/src/Internal/ModelVersion.cs @@ -0,0 +1,13 @@ +using System; +using System.Runtime.InteropServices; + +namespace Femyou.Internal +{ + public interface IModelVersion + { + string CoSimulationElementName { get; } + string GuidAttributeName { get; } + string RelativePath(string name, Architecture architecture, PlatformID platform); + Library Load(string path); + } +} \ No newline at end of file diff --git a/src/Internal/ModelVersion2.cs b/src/Internal/ModelVersion2.cs new file mode 100644 index 0000000..e6aefce --- /dev/null +++ b/src/Internal/ModelVersion2.cs @@ -0,0 +1,29 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Femyou.Internal +{ + public class ModelVersion2 : IModelVersion + { + public string CoSimulationElementName { get; } = "CoSimulation"; + public string GuidAttributeName { get; } = "guid"; + public string RelativePath(string name, Architecture architecture, PlatformID platform) => + platform switch + { + PlatformID.Unix => Path.Combine("binaries", "linux" + MapArchitecture(architecture), name + ".so"), + PlatformID.Win32NT => Path.Combine("binaries", "win" + MapArchitecture(architecture), name + ".dll"), + _ => throw new FmuException($"Unsupported operating system {platform}"), + }; + + public Library Load(string path) => new Library2(path); + + private string MapArchitecture(Architecture architecture) => + architecture switch + { + Architecture.X86 => "32", + Architecture.X64 => "64", + _ => throw new FmuException($"Unsupported architecture {architecture}"), + }; + } +} \ No newline at end of file diff --git a/src/Internal/ModelVersion3.cs b/src/Internal/ModelVersion3.cs new file mode 100644 index 0000000..7624ae2 --- /dev/null +++ b/src/Internal/ModelVersion3.cs @@ -0,0 +1,30 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Femyou.Internal +{ + public class ModelVersion3 : IModelVersion + { + public string CoSimulationElementName { get; } = "BasicCoSimulation"; + public string GuidAttributeName { get; } = "instantiationToken"; + public Library Load(string path) => new Library3(path); + + public string RelativePath(string name, Architecture architecture, PlatformID platform) => + platform switch + { + PlatformID.Unix => Path.Combine("binaries", MapArchitecture(architecture)+"-linux", name + ".so"), + PlatformID.Win32NT => Path.Combine("binaries", MapArchitecture(architecture)+"-windows", name + ".dll"), + _ => throw new FmuException($"Unsupported operating system {platform}"), + }; + + private string MapArchitecture(Architecture architecture) => + architecture switch + { + Architecture.X86 => "x86", + Architecture.X64 => "x86_64", + Architecture.Arm64 => "aarch64", + _ => throw new FmuException($"Unsupported architecture {architecture}"), + }; + } +} \ No newline at end of file diff --git a/src/Internal/Platform.cs b/src/Internal/Platform.cs deleted file mode 100644 index bda1ced..0000000 --- a/src/Internal/Platform.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.IO; - -namespace Femyou.Internal -{ - public class Platform - { - public static string GetLibraryPath(PlatformID platformId, bool is64Bits, string baseFolder, string name) => - platformId switch - { - PlatformID.Unix => Path.Combine(baseFolder, "binaries", "linux" + Bits(is64Bits), name + ".so"), - PlatformID.Win32NT => Path.Combine(baseFolder, "binaries", "win" + Bits(is64Bits), name + ".dll"), - _ => throw new ArgumentException($"Unsupported platform {platformId}"), - }; - - private static string Bits(bool is64Bits) => is64Bits ? "64" : "32"; - } -} \ No newline at end of file diff --git a/src/Internal/Variable.cs b/src/Internal/Variable.cs index 348fbe9..486a800 100644 --- a/src/Internal/Variable.cs +++ b/src/Internal/Variable.cs @@ -3,7 +3,7 @@ namespace Femyou.Internal { - class Variable : IVariable + public class Variable : IVariable { public Variable(XElement xElement) { @@ -19,6 +19,13 @@ public Variable(XElement xElement) } } + public Variable(string name, string description, uint valueReference) + { + Name = name; + Description = description; + ValueReference = valueReference; + } + public string Name { get; } public string Description { get; } public uint ValueReference { get; } diff --git a/src/Interop/Marshalling.cs b/src/Interop/Marshalling.cs new file mode 100644 index 0000000..318df22 --- /dev/null +++ b/src/Interop/Marshalling.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Femyou +{ + static class Marshalling + { + public static IntPtr[] CreateArray(int size) => Enumerable.Repeat(IntPtr.Zero, size).ToArray(); + public static string GetString(IntPtr stringPtr) => Marshal.PtrToStringAnsi(stringPtr); + public static IntPtr AllocateMemory(UInt64 nobj, UInt64 size) => Marshal.AllocHGlobal((int)(nobj * size)); + public static void FreeMemory(IntPtr obj) => Marshal.FreeHGlobal(obj); + } +} \ No newline at end of file diff --git a/src/fmi/fmi2FunctionTypes.cs b/src/Interop/fmi2FunctionTypes.cs similarity index 88% rename from src/fmi/fmi2FunctionTypes.cs rename to src/Interop/fmi2FunctionTypes.cs index 0bc0ac4..e1afd8c 100644 --- a/src/fmi/fmi2FunctionTypes.cs +++ b/src/Interop/fmi2FunctionTypes.cs @@ -1,6 +1,7 @@ // ReSharper disable InconsistentNaming using System; +using System.Runtime.InteropServices; using size_t = System.UInt64; /* Type definitions of variables passed as arguments @@ -20,21 +21,16 @@ using fmi2Component = System.IntPtr; /* Pointer to FMU instance */ using fmi2ComponentEnvironment = System.IntPtr; /* Pointer to FMU environment */ -using fmi2FMUstate = System.IntPtr; /* Pointer to internal FMU state */ +/* Pointer to internal FMU state */ using fmi2ValueReference = System.UInt32; using fmi2Real = System.Double; using fmi2Integer = System.Int32; -using fmi2Boolean = System.Int32; using fmi2String = System.String; /* Type definitions */ using fmi2Status = System.Int32; -using fmi2StatusKind = System.Int32; - -using System.Runtime.InteropServices; -using System.Linq; -namespace Femyou +namespace Femyou.Interop { static class FMI2 { @@ -93,7 +89,6 @@ public delegate fmi2Status fmi2SetupExperimentTYPE(fmi2Component c, public delegate fmi2Status fmi2EnterInitializationModeTYPE(fmi2Component c); public delegate fmi2Status fmi2ExitInitializationModeTYPE(fmi2Component c); public delegate fmi2Status fmi2TerminateTYPE(fmi2Component c); - public delegate fmi2Status fmi2ResetTYPE(fmi2Component c); /* Getting and setting variable values */ public delegate fmi2Status fmi2GetRealTYPE(fmi2Component c, fmi2ValueReference[] vr, size_t nvr, fmi2Real[] value); @@ -118,13 +113,5 @@ public delegate fmi2Status fmi2DoStepTYPE(fmi2Component c, fmi2Real communicationStepSize, fmi2Boolean noSetFMUStatePriorToCurrentPoint); } - - static class Marshalling - { - public static IntPtr[] CreateArray(int size) => Enumerable.Repeat(IntPtr.Zero, size).ToArray(); - public static string GetString(IntPtr stringPtr) => Marshal.PtrToStringAnsi(stringPtr); - public static IntPtr AllocateMemory(size_t nobj, size_t size) => Marshal.AllocHGlobal((int)(nobj * size)); - public static void FreeMemory(IntPtr obj) => Marshal.FreeHGlobal(obj); - } } diff --git a/src/Interop/fmi3FunctionTypes.cs b/src/Interop/fmi3FunctionTypes.cs new file mode 100644 index 0000000..5dcfdee --- /dev/null +++ b/src/Interop/fmi3FunctionTypes.cs @@ -0,0 +1,97 @@ +// ReSharper disable InconsistentNaming + +using System; +using System.Runtime.InteropServices; +using size_t = System.UInt64; +using fmi3Instance = System.IntPtr; /* Pointer to FMU instance */ +using fmi3InstanceEnvironment = System.IntPtr; /* Pointer to FMU environment */ +using fmi3ValueReference = System.UInt32; +using fmi3Float64 = System.Double; +using fmi3Int32 = System.Int32; +using fmi3Integer = System.Int32; +using fmi3String = System.String; +using fmi3Status = System.Int32; + +namespace Femyou.Interop +{ + static class FMI3 + { + public enum fmi3Boolean + { + fmi3False = 0, + fmi3True = 1 + } + + public delegate void fmi3CallbackLogMessage(fmi3InstanceEnvironment componentEnvironment, + fmi3String instanceName, + fmi3Status status, + fmi3String category, + fmi3String message); + + public delegate fmi3Instance fmi3InstantiateBasicCoSimulationTYPE( + fmi3String instanceName, + fmi3String instantiationToken, + fmi3String resourceLocation, + fmi3Boolean visible, + fmi3Boolean loggingOn, + fmi3Boolean intermediateVariableGetRequired, + fmi3Boolean intermediateInternalVariableGetRequired, + fmi3Boolean intermediateVariableSetRequired, + fmi3InstanceEnvironment instanceEnvironment, + fmi3CallbackLogMessage logMessage, + IntPtr intermediateUpdate); + + public delegate void fmi3FreeInstanceTYPE(fmi3Instance c); + + /* Enter and exit initialization mode, terminate and reset */ + + public delegate fmi3Status fmi3EnterInitializationModeTYPE(fmi3Instance instance, + fmi3Boolean toleranceDefined, + fmi3Float64 tolerance, + fmi3Float64 startTime, + fmi3Boolean stopTimeDefined, + fmi3Float64 stopTime); + + public delegate fmi3Status fmi3ExitInitializationModeTYPE(fmi3Instance instance); + + public delegate fmi3Status fmi3EnterStepModeTYPE(fmi3Instance instance); + + + public delegate fmi3Status fmi3TerminateTYPE(fmi3Instance c); + + + public delegate fmi3Status fmi3DoStepTYPE(fmi3Instance instance, + fmi3Float64 currentCommunicationPoint, + fmi3Float64 communicationStepSize, + fmi3Boolean noSetFMUStatePriorToCurrentPoint, + IntPtr terminate, + IntPtr earlyReturn, + IntPtr lastSuccessfulTime); + + /* Getting and setting variable values */ + public delegate fmi3Status fmi3GetFloat64TYPE(fmi3Instance c, fmi3ValueReference[] vr, size_t nvr, + fmi3Float64[] value, size_t nv); + + public delegate fmi3Status fmi3GetInt32TYPE(fmi3Instance c, fmi3ValueReference[] vr, size_t nvr, + fmi3Integer[] value, size_t nv); + + public delegate fmi3Status fmi3GetBooleanTYPE(fmi3Instance c, fmi3ValueReference[] vr, size_t nvr, + fmi3Boolean[] value, size_t nv); + + public delegate fmi3Status fmi3GetStringTYPE(fmi3Instance c, fmi3ValueReference[] vr, size_t nvr, IntPtr[] value, + size_t nv); + + + public delegate fmi3Status fmi3SetFloat64TYPE(fmi3Instance c, fmi3ValueReference[] vr, size_t nvr, + fmi3Float64[] value, size_t nv); + + public delegate fmi3Status fmi3SetInt32TYPE(fmi3Instance c, fmi3ValueReference[] vr, size_t nvr, + fmi3Integer[] value, size_t nv); + + public delegate fmi3Status fmi3SetBooleanTYPE(fmi3Instance c, fmi3ValueReference[] vr, size_t nvr, + fmi3Boolean[] value, size_t nv); + + public delegate fmi3Status fmi3SetStringTYPE(fmi3Instance c, fmi3ValueReference[] vr, size_t nvr, + [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr)] fmi3String[] value, size_t nv); + } +} \ No newline at end of file diff --git a/tests/CallbacksTests.cs b/tests/CallbacksTests.cs index dd99fdd..b05e4e5 100644 --- a/tests/CallbacksTests.cs +++ b/tests/CallbacksTests.cs @@ -1,60 +1,68 @@ using System; +using Femyou.Internal; using NUnit.Framework; -namespace Femyou.Tests +namespace Femyou.Tests; + +[TestFixture(2)] +[TestFixture(3)] +public class CallbacksTests { - public class CallbacksTests + public CallbacksTests(int version) { - [Test] - public void LogWhenVariableCannotBeSet() - { - var spyCallback = new SpyCallback(); - using var model = Model.Load(TestTools.GetFmuPath("BouncingBall.fmu")); - using var instance = model.CreateCoSimulationInstance("my name", spyCallback); - try - { - instance.WriteReal((model.Variables["v_min"], 1)); - } - catch(FmuException) - { - Assert.That(spyCallback.Instance.Name, Is.EqualTo("my name")); - Assert.That(spyCallback.Status, Is.EqualTo(Status.Error)); - Assert.That(spyCallback.Category, Is.EqualTo("logStatusError")); - Assert.That(spyCallback.Message, - Is.EqualTo("Variable v_min (value reference 4) is constant and cannot be set.")); - return; - } + _getFmuPath = name => TestTools.GetFmuPath(version, name); + } + private Func _getFmuPath; - Assert.Fail("Exception shall have been raised"); + [Test] + public void LogWhenVariableCannotBeSet() + { + var spyCallback = new SpyCallback(); + using var model = Model.Load(_getFmuPath("BouncingBall.fmu")); + using var instance = model.CreateCoSimulationInstance("my name", spyCallback); + instance.StartTime(0); + try + { + instance.WriteReal((new Variable("foo","",42), 1)); } - - [Test] - public void CallbacksMustSurviveGarbageCollection() + catch(FmuException) { - var spyCallback = new SpyCallback(); - using var model = Model.Load(TestTools.GetFmuPath("BouncingBall.fmu")); - using var instance = model.CreateCoSimulationInstance("my name", spyCallback); - GC.Collect(); - Assert.Throws(() => - { - instance.WriteReal((model.Variables["v_min"], 1)); - }); + Assert.That(spyCallback.Instance.Name, Is.EqualTo("my name")); + Assert.That(spyCallback.Status, Is.EqualTo(Status.Error)); + Assert.That(spyCallback.Category, Is.EqualTo("logStatusError")); + return; } - class SpyCallback : ICallbacks + Assert.Fail("Exception shall have been raised"); + } + + [Test] + public void CallbacksMustSurviveGarbageCollection() + { + var spyCallback = new SpyCallback(); + using var model = Model.Load(_getFmuPath("BouncingBall.fmu")); + using var instance = model.CreateCoSimulationInstance("my name", spyCallback); + instance.StartTime(0); + GC.Collect(); + Assert.Throws(() => { - public IInstance Instance { get; private set; } - public Status Status { get; private set; } - public string Category { get; private set; } - public string Message { get; private set; } + instance.WriteReal((new Variable("foo","",42), 1)); + }); + } - public void Logger(IInstance instance, Status status, string category, string message) - { - Instance = instance; - Status = status; - Category = category; - Message = message; - } + class SpyCallback : ICallbacks + { + public IInstance Instance { get; private set; } + public Status Status { get; private set; } + public string Category { get; private set; } + public string Message { get; private set; } + + public void Logger(IInstance instance, Status status, string category, string message) + { + Instance = instance; + Status = status; + Category = category; + Message = message; } } } \ No newline at end of file diff --git a/tests/InstanceTests.cs b/tests/InstanceTests.cs index 2d9c9a2..306edd2 100644 --- a/tests/InstanceTests.cs +++ b/tests/InstanceTests.cs @@ -1,140 +1,149 @@ using System; using NUnit.Framework; -using Femyou; using System.Linq; using System.Collections.Generic; -namespace Femyou.Tests +namespace Femyou.Tests; + +[TestFixture(2)] +[TestFixture(3)] +public class InstanceTests { - public class InstanceTests + public InstanceTests(int version) { - [TestCaseSource("DefaultValuesTestCases")] - public void InstanceHasDefaultValues((string filename, string variableName, Func, IEnumerable> reader, object expectedDefaultValue, Action> writer, object newValue) t) - { - using var model = Model.Load(TestTools.GetFmuPath(t.filename)); - using var instance = Tools.CreateInstance(model, "example"); - var variables = new IVariable[] { model.Variables[t.variableName] }; - var actualValue = t.reader(instance, variables).First(); - Assert.That(actualValue, Is.EqualTo(t.expectedDefaultValue)); - } + _getFmuPath = name => TestTools.GetFmuPath(version, name); + } + private Func _getFmuPath; - [TestCaseSource("DefaultValuesTestCases")] - public void ChangeInstanceDefaultValues((string filename, string variableName, Func, IEnumerable> reader, object expectedDefaultValue, Action> writer, object newValue) t) - { - using var model = Model.Load(TestTools.GetFmuPath(t.filename)); - using var instance = Tools.CreateInstance(model, "example"); - var variables = new IVariable[] { model.Variables[t.variableName] }; - t.writer(instance, variables); - var actualValue = t.reader(instance, variables).First(); - Assert.That(actualValue, Is.EqualTo(t.newValue)); - } + [TestCaseSource("DefaultValuesTestCases")] + public void InstanceHasDefaultValues((string filename, string variableName, Func, IEnumerable> reader, object expectedDefaultValue, Action> writer, object newValue) t) + { + using var model = Model.Load(_getFmuPath(t.filename)); + using var instance = Tools.CreateInstance(model, "example"); + var variables = new IVariable[] { model.Variables[t.variableName] }; + instance.StartTime(0.0); + var actualValue = t.reader(instance, variables).First(); + Assert.That(actualValue, Is.EqualTo(t.expectedDefaultValue)); + } - static readonly (string, string, Func, IEnumerable>, object, Action>, object)[] DefaultValuesTestCases = - { - ("Feedthrough.fmu", "bool_in", (i,v) => i.ReadBoolean(v).Cast(), false, (i,vs) => i.WriteBoolean(vs.Select(v => (v,true))), true), - ("Feedthrough.fmu", "string_param", (i,v) => i.ReadString(v), "Set me!", (i,vs) => i.WriteString(vs.Select(v => (v,"Foo"))), "Foo"), - ("BouncingBall.fmu", "g", (i,v) => i.ReadReal(v).Cast(), -9.81, (i,vs) => i.WriteReal(vs.Select(v => (v,3.46))), 3.46), - ("Stair.fmu", "counter", (i,v) => i.ReadInteger(v).Cast(), 1, (i,vs) => {}, 1), - ("Feedthrough.fmu", "int_in", (i,v) => i.ReadInteger(v).Cast(), 0, (i,vs) => i.WriteInteger(vs.Select(v => (v,28))), 28) - }; + [TestCaseSource("DefaultValuesTestCases")] + public void ChangeInstanceDefaultValues((string filename, string variableName, Func, IEnumerable> reader, object expectedDefaultValue, Action> writer, object newValue) t) + { + using var model = Model.Load(_getFmuPath(t.filename)); + using var instance = Tools.CreateInstance(model, "example"); + var variables = new IVariable[] { model.Variables[t.variableName] }; + instance.StartTime(0.0); + t.writer(instance, variables); + var actualValue = t.reader(instance, variables).First(); + Assert.That(actualValue, Is.EqualTo(t.newValue)); + } - [Test] - public void ReadMultipleValuesInOneCall() - { - using var model = Model.Load(TestTools.GetFmuPath("VanDerPol.fmu")); - using var instance = Tools.CreateInstance(model, "example"); - var actualValues = instance.ReadReal(model.Variables["x0"], model.Variables["x1"], model.Variables["mu"]); - Assert.That(actualValues, Is.EqualTo(new double[] { 2, 0, 1 })); - } + static readonly (string, string, Func, IEnumerable>, object, Action>, object)[] DefaultValuesTestCases = + { + ("Feedthrough.fmu", "bool_in", (i,v) => i.ReadBoolean(v).Cast(), false, (i,vs) => i.WriteBoolean(vs.Select(v => (v,true))), true), + ("Feedthrough.fmu", "string_param", (i,v) => i.ReadString(v), "Set me!", (i,vs) => i.WriteString(vs.Select(v => (v,"Foo"))), "Foo"), + ("BouncingBall.fmu", "g", (i,v) => i.ReadReal(v).Cast(), -9.81, (i,vs) => i.WriteReal(vs.Select(v => (v,3.46))), 3.46), + ("Stair.fmu", "counter", (i,v) => i.ReadInteger(v).Cast(), 1, (i,vs) => {}, 1), + ("Feedthrough.fmu", "int_in", (i,v) => i.ReadInteger(v).Cast(), 0, (i,vs) => i.WriteInteger(vs.Select(v => (v,28))), 28) + }; + + [Test] + public void ReadMultipleValuesInOneCall() + { + using var model = Model.Load(_getFmuPath("VanDerPol.fmu")); + using var instance = Tools.CreateInstance(model, "example"); + instance.StartTime(0.0); + var actualValues = instance.ReadReal(model.Variables["x0"], model.Variables["x1"], model.Variables["mu"]); + Assert.That(actualValues, Is.EqualTo(new double[] { 2, 0, 1 })); + } - // ReSharper disable InconsistentNaming + // ReSharper disable InconsistentNaming - [Test] - public void FeedthroughReferenceScenario() // see https://github.com/modelica/Reference-FMUs/tree/master/Feedthrough + [Test] + public void FeedthroughReferenceScenario() // see https://github.com/modelica/Reference-FMUs/tree/master/Feedthrough + { + using var model = Model.Load(_getFmuPath("Feedthrough.fmu")); + using var instance = Tools.CreateInstance(model, "reference"); + instance.WriteReal((model.Variables["real_fixed_param"], 1)); + instance.WriteString((model.Variables["string_param"], "FMI is awesome!")); + var real_tunable_param = model.Variables["real_tunable_param"]; + var real_continuous_in = model.Variables["real_continuous_in"]; + var real_continuous_out = model.Variables["real_continuous_out"]; + var real_discrete_in = model.Variables["real_discrete_in"]; + var real_discrete_out = model.Variables["real_discrete_out"]; + var int_in = model.Variables["int_in"]; + var int_out = model.Variables["int_out"]; + var bool_in = model.Variables["bool_in"]; + var bool_out = model.Variables["bool_out"]; + instance.StartTime(0.0); + foreach (var pt in feedthroughScenario) { - using var model = Model.Load(TestTools.GetFmuPath("Feedthrough.fmu")); - using var instance = Tools.CreateInstance(model, "reference"); - instance.WriteReal((model.Variables["real_fixed_param"], 1)); - instance.WriteString((model.Variables["string_param"], "FMI is awesome!")); - var real_tunable_param = model.Variables["real_tunable_param"]; - var real_continuous_in = model.Variables["real_continuous_in"]; - var real_continuous_out = model.Variables["real_continuous_out"]; - var real_discrete_in = model.Variables["real_discrete_in"]; - var real_discrete_out = model.Variables["real_discrete_out"]; - var int_in = model.Variables["int_in"]; - var int_out = model.Variables["int_out"]; - var bool_in = model.Variables["bool_in"]; - var bool_out = model.Variables["bool_out"]; - instance.StartTime(0.0); - foreach (var pt in feedthroughScenario) - { - instance.AdvanceTime(pt.time - instance.CurrentTime); - instance.WriteReal((real_tunable_param, pt.real_tunable_param), (real_continuous_in, pt.input.real_continuous), (real_discrete_in, pt.input.real_discrete)); - instance.WriteInteger((int_in, pt.input.integer)); - instance.WriteBoolean((bool_in, pt.input.boolean)); - var actual_reals = instance.ReadReal(real_continuous_out, real_discrete_out).ToArray(); - var actual_int = instance.ReadInteger(int_out); - var actual_bool = instance.ReadBoolean(bool_out); - Assert.That(actual_reals.First(), Is.EqualTo(pt.expectedOutput.real_continuous)); - Assert.That(actual_reals.Last(), Is.EqualTo(pt.expectedOutput.real_discrete)); - Assert.That(actual_int.First(), Is.EqualTo(pt.expectedOutput.integer)); - Assert.That(actual_bool.First(), Is.EqualTo(pt.expectedOutput.boolean)); - } + instance.AdvanceTime(pt.time - instance.CurrentTime); + instance.WriteReal((real_tunable_param, pt.real_tunable_param), (real_continuous_in, pt.input.real_continuous), (real_discrete_in, pt.input.real_discrete)); + instance.WriteInteger((int_in, pt.input.integer)); + instance.WriteBoolean((bool_in, pt.input.boolean)); + var actual_reals = instance.ReadReal(real_continuous_out, real_discrete_out).ToArray(); + var actual_int = instance.ReadInteger(int_out); + var actual_bool = instance.ReadBoolean(bool_out); + Assert.That(actual_reals.First(), Is.EqualTo(pt.expectedOutput.real_continuous)); + Assert.That(actual_reals.Last(), Is.EqualTo(pt.expectedOutput.real_discrete)); + Assert.That(actual_int.First(), Is.EqualTo(pt.expectedOutput.integer)); + Assert.That(actual_bool.First(), Is.EqualTo(pt.expectedOutput.boolean)); } + } - readonly FTPoint[] feedthroughScenario = { - FTPoint.At( time:0.0, real_tunable_param:0, input:FTData.Is(0.0,0,0,false), expectedOutput: FTData.Is(1.0,0,0,false) ), - FTPoint.At( time:0.2, real_tunable_param:0, input:FTData.Is(0.0,0,0,false), expectedOutput: FTData.Is(1.0,0,0,false) ), - FTPoint.At( time:0.4, real_tunable_param:0, input:FTData.Is(0.0,0,0,false), expectedOutput: FTData.Is(1.0,0,0,false) ), - FTPoint.At( time:0.5, real_tunable_param:0, input:FTData.Is(0.0,0,0,false), expectedOutput: FTData.Is(1.0,0,0,false) ), - FTPoint.At( time:0.5, real_tunable_param:0, input:FTData.Is(2.0,0,0,false), expectedOutput: FTData.Is(3.0,0,0,false) ), - FTPoint.At( time:0.6, real_tunable_param:0, input:FTData.Is(1.8,0,0,false), expectedOutput: FTData.Is(2.8,0,0,false) ), - FTPoint.At( time:0.8, real_tunable_param:0, input:FTData.Is(1.4,0,0,false), expectedOutput: FTData.Is(2.4,0,0,false) ), - FTPoint.At( time:1.0, real_tunable_param:0, input:FTData.Is(1.0,0,0,false), expectedOutput: FTData.Is(2.0,0,0,false) ), - FTPoint.At( time:1.0, real_tunable_param:0, input:FTData.Is(1.0,1,1,true), expectedOutput: FTData.Is(2.0,1,1,true) ), - FTPoint.At( time:1.2, real_tunable_param:0, input:FTData.Is(1.0,1,1,true), expectedOutput: FTData.Is(2.0,1,1,true) ), - FTPoint.At( time:1.4, real_tunable_param:0, input:FTData.Is(1.0,1,1,true), expectedOutput: FTData.Is(2.0,1,1,true) ), - FTPoint.At( time:1.5, real_tunable_param:0, input:FTData.Is(1.0,1,1,true), expectedOutput: FTData.Is(2.0,1,1,true) ), - FTPoint.At( time:1.5, real_tunable_param:-1, input:FTData.Is(1.0,1,1,true), expectedOutput: FTData.Is(1.0,1,1,true) ), - FTPoint.At( time:1.6, real_tunable_param:-1, input:FTData.Is(1.0,1,1,true), expectedOutput: FTData.Is(1.0,1,1,true) ), - FTPoint.At( time:1.8, real_tunable_param:-1, input:FTData.Is(1.0,1,1,true), expectedOutput: FTData.Is(1.0,1,1,true) ), - FTPoint.At( time:2.0, real_tunable_param:-1, input:FTData.Is(1.0,1,1,true), expectedOutput: FTData.Is(1.0,1,1,true) ) - }; + readonly FTPoint[] feedthroughScenario = { + FTPoint.At( time:0.0, real_tunable_param:0, input:FTData.Is(0.0,0,0,false), expectedOutput: FTData.Is(1.0,0,0,false) ), + FTPoint.At( time:0.2, real_tunable_param:0, input:FTData.Is(0.0,0,0,false), expectedOutput: FTData.Is(1.0,0,0,false) ), + FTPoint.At( time:0.4, real_tunable_param:0, input:FTData.Is(0.0,0,0,false), expectedOutput: FTData.Is(1.0,0,0,false) ), + FTPoint.At( time:0.5, real_tunable_param:0, input:FTData.Is(0.0,0,0,false), expectedOutput: FTData.Is(1.0,0,0,false) ), + FTPoint.At( time:0.5, real_tunable_param:0, input:FTData.Is(2.0,0,0,false), expectedOutput: FTData.Is(3.0,0,0,false) ), + FTPoint.At( time:0.6, real_tunable_param:0, input:FTData.Is(1.8,0,0,false), expectedOutput: FTData.Is(2.8,0,0,false) ), + FTPoint.At( time:0.8, real_tunable_param:0, input:FTData.Is(1.4,0,0,false), expectedOutput: FTData.Is(2.4,0,0,false) ), + FTPoint.At( time:1.0, real_tunable_param:0, input:FTData.Is(1.0,0,0,false), expectedOutput: FTData.Is(2.0,0,0,false) ), + FTPoint.At( time:1.0, real_tunable_param:0, input:FTData.Is(1.0,1,1,true), expectedOutput: FTData.Is(2.0,1,1,true) ), + FTPoint.At( time:1.2, real_tunable_param:0, input:FTData.Is(1.0,1,1,true), expectedOutput: FTData.Is(2.0,1,1,true) ), + FTPoint.At( time:1.4, real_tunable_param:0, input:FTData.Is(1.0,1,1,true), expectedOutput: FTData.Is(2.0,1,1,true) ), + FTPoint.At( time:1.5, real_tunable_param:0, input:FTData.Is(1.0,1,1,true), expectedOutput: FTData.Is(2.0,1,1,true) ), + FTPoint.At( time:1.5, real_tunable_param:-1, input:FTData.Is(1.0,1,1,true), expectedOutput: FTData.Is(1.0,1,1,true) ), + FTPoint.At( time:1.6, real_tunable_param:-1, input:FTData.Is(1.0,1,1,true), expectedOutput: FTData.Is(1.0,1,1,true) ), + FTPoint.At( time:1.8, real_tunable_param:-1, input:FTData.Is(1.0,1,1,true), expectedOutput: FTData.Is(1.0,1,1,true) ), + FTPoint.At( time:2.0, real_tunable_param:-1, input:FTData.Is(1.0,1,1,true), expectedOutput: FTData.Is(1.0,1,1,true) ) + }; - struct FTData + struct FTData + { + public double real_continuous; + public double real_discrete; + public int integer; + public bool boolean; + public static FTData Is(double real_continuous, double real_discrete, int integer, bool boolean) { - public double real_continuous; - public double real_discrete; - public int integer; - public bool boolean; - public static FTData Is(double real_continuous, double real_discrete, int integer, bool boolean) - { - FTData dt; - dt.real_continuous = real_continuous; - dt.real_discrete = real_discrete; - dt.integer = integer; - dt.boolean = boolean; - return dt; - } + FTData dt; + dt.real_continuous = real_continuous; + dt.real_discrete = real_discrete; + dt.integer = integer; + dt.boolean = boolean; + return dt; } + } - struct FTPoint + struct FTPoint + { + public double time; + public double real_tunable_param; + public FTData input; + public FTData expectedOutput; + public static FTPoint At(double time, double real_tunable_param, FTData input, FTData expectedOutput) { - public double time; - public double real_tunable_param; - public FTData input; - public FTData expectedOutput; - public static FTPoint At(double time, double real_tunable_param, FTData input, FTData expectedOutput) - { - FTPoint pt; - pt.time = time; - pt.real_tunable_param = real_tunable_param; - pt.input = input; - pt.expectedOutput = expectedOutput; - return pt; - } + FTPoint pt; + pt.time = time; + pt.real_tunable_param = real_tunable_param; + pt.input = input; + pt.expectedOutput = expectedOutput; + return pt; } - } + } \ No newline at end of file diff --git a/tests/ModelTests.cs b/tests/ModelTests.cs index 53fea28..79ef37d 100644 --- a/tests/ModelTests.cs +++ b/tests/ModelTests.cs @@ -1,26 +1,35 @@ +using System; using NUnit.Framework; using System.Linq; namespace Femyou.Tests { + [TestFixture(2)] + [TestFixture(3)] public class ModelTests { - [TestCase("BouncingBall.fmu", "BouncingBall", "This model calculates the trajectory, over time, of a ball dropped from a height of 1 m.")] - [TestCase("VanDerPol.fmu", "Van der Pol oscillator", "This model implements the van der Pol oscillator")] + public ModelTests(int version) + { + _getFmuPath = name => TestTools.GetFmuPath(version, name); + } + private readonly Func _getFmuPath; + + [TestCase("BouncingBall.fmu", "bouncingball", "This model calculates the trajectory, over time, of a ball dropped from a height of 1 m.")] + [TestCase("VanDerPol.fmu", "van der pol oscillator", "This model implements the van der Pol oscillator")] public void ModelHasNameAndDescription(string filename, string expectedName, string expectedDescription) { - var fmuPath = TestTools.GetFmuPath(filename); + var fmuPath = _getFmuPath(filename); using var model = Model.Load(fmuPath); - Assert.That(model.Name, Is.EqualTo(expectedName)); - Assert.That(model.Description, Is.EqualTo(expectedDescription)); + Assert.That(model.Name.ToLower(), Is.EqualTo(expectedName)); + Assert.That(model.Description, Is.Null.Or.EqualTo(expectedDescription)); } [Test] public void ModelHasVariables() { - var fmuPath = TestTools.GetFmuPath("Feedthrough.fmu"); + var fmuPath = _getFmuPath("Feedthrough.fmu"); using var model = Model.Load(fmuPath); - Assert.That(model.Variables.Count(), Is.EqualTo(11)); + Assert.That(model.Variables.Count(), Is.GreaterThanOrEqualTo(11)); Assert.That(model.Variables["string_param"].Description, Is.EqualTo("String parameter")); Assert.That(model.Variables["bool_out"].Description, Is.EqualTo("boolean output")); } diff --git a/tests/PlatformsTests.cs b/tests/PlatformsTests.cs index f41708b..55904e6 100644 --- a/tests/PlatformsTests.cs +++ b/tests/PlatformsTests.cs @@ -1,28 +1,48 @@ using System; +using System.Runtime.InteropServices; using NUnit.Framework; using Femyou.Internal; -namespace Femyou.Tests +namespace Femyou.Tests; + +public class PlatformTests { - public class PlatformTests + [TestCase(PlatformID.Unix,Architecture.X64,"bar","binaries/linux64/bar.so")] + [TestCase(PlatformID.Unix,Architecture.X86,"bar","binaries/linux32/bar.so")] + [TestCase(PlatformID.Win32NT,Architecture.X64,"bar", "binaries/win64/bar.dll")] + [TestCase(PlatformID.Win32NT,Architecture.X86,"bar", "binaries/win32/bar.dll")] + public void GetRelativePath2(PlatformID platformId, Architecture architecture, string fmuName, string expectedLibraryPath) + { + Assert.That( + new ModelVersion2().RelativePath( fmuName, architecture, platformId), + Is.EqualTo(expectedLibraryPath) + ); + } + + [TestCase(PlatformID.Unix,Architecture.X64,"bar","binaries/x86_64-linux/bar.so")] + [TestCase(PlatformID.Unix,Architecture.X86,"bar","binaries/x86-linux/bar.so")] + [TestCase(PlatformID.Win32NT,Architecture.X64,"bar", "binaries/x86_64-windows/bar.dll")] + [TestCase(PlatformID.Win32NT,Architecture.X86,"bar", "binaries/x86-windows/bar.dll")] + public void GetRelativePath3(PlatformID platformId, Architecture architecture, string fmuName, string expectedLibraryPath) + { + Assert.That( + new ModelVersion3().RelativePath( fmuName, architecture, platformId), + Is.EqualTo(expectedLibraryPath) + ); + } + + [TestCase(PlatformID.MacOSX,Architecture.X64,"bar")] + [TestCase(PlatformID.WinCE,Architecture.X86,"bar")] + public void Unsupported2(PlatformID platformId, Architecture architecture, string fmuName) { - [TestCase(PlatformID.Unix,true,"/home/foo","bar","/home/foo/binaries/linux64/bar.so")] - [TestCase(PlatformID.Unix,false,"/home/foo","bar","/home/foo/binaries/linux32/bar.so")] - [TestCase(PlatformID.Win32NT,true,"C:\\foo","bar", "C:\\foo/binaries/win64/bar.dll")] - [TestCase(PlatformID.Win32NT,false,"C:\\foo","bar", "C:\\foo/binaries/win32/bar.dll")] - public void GetLibraryPath(PlatformID platformId, bool is64Bits, string baseFolder, string fmuName, string expectedLibraryPath) - { - Assert.That( - Platform.GetLibraryPath(platformId, is64Bits, baseFolder, fmuName), - Is.EqualTo(expectedLibraryPath) - ); - } + Assert.Throws(() => new ModelVersion2().RelativePath(fmuName, architecture, platformId)); + } - [TestCase(PlatformID.MacOSX,"/home/foo","bar")] - [TestCase(PlatformID.WinCE,"C:\\foo","bar")] - public void Unsupported(PlatformID platformId, string baseFolder, string fmuName) - { - Assert.Throws(() => Platform.GetLibraryPath(platformId,true,baseFolder,fmuName)); - } + [TestCase(PlatformID.MacOSX,Architecture.X64,"bar")] + [TestCase(PlatformID.WinCE,Architecture.X86,"bar")] + public void Unsupported3(PlatformID platformId, Architecture architecture, string fmuName) + { + Assert.Throws(() => new ModelVersion3().RelativePath(fmuName, architecture, platformId)); } + } \ No newline at end of file diff --git a/tests/TestTools.cs b/tests/TestTools.cs index b97ccea..bb10ba8 100644 --- a/tests/TestTools.cs +++ b/tests/TestTools.cs @@ -6,8 +6,10 @@ namespace Femyou.Tests { public static class TestTools { - public static string GetFmuPath(string filename) => Path.Combine(FmuFolder, filename); - public static string FmuFolder => Path.Combine(BaseFolder, "FMU", "bin", "dist"); + public static string GetFmuPath(int version, string filename) => + Path.Combine(FmuFolder(version), filename); + public static string FmuFolder(int version) => + Path.Combine(BaseFolder, "FMU", $"bin{version}", "dist"); public static string BaseFolder => Tools .GetBaseFolder(new Uri(Assembly.GetExecutingAssembly().Location).AbsolutePath, nameof(Femyou)); }