1 package org.simantics.fmil.core;
4 import java.io.IOException;
5 import java.io.RandomAccessFile;
7 import java.nio.channels.FileChannel;
8 import java.nio.channels.FileLock;
9 import java.nio.file.Paths;
10 import java.util.ArrayList;
11 import java.util.HashSet;
12 import java.util.List;
14 import java.util.UUID;
16 import org.eclipse.core.runtime.FileLocator;
17 import org.eclipse.core.runtime.Path;
18 import org.eclipse.core.runtime.Platform;
19 import org.osgi.framework.Bundle;
20 import org.simantics.fmil.core.ExecEnvironment.OSType;
21 import org.simantics.utils.FileUtils;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
25 import gnu.trove.list.array.TIntArrayList;
26 import gnu.trove.map.hash.TObjectIntHashMap;
31 private static final Logger LOGGER = LoggerFactory.getLogger(FMIL.class);
36 private static int OK = 0;
37 private static int ERROR = 1;
38 private static String UNSATISFIED_LINK = "Method not found. DLL might not be loaded properly.";
39 public static final String TEMP_FMU_DIRECTORY_NAME = "fmil";
40 public static String TEMP_FMU_COMMON_DIRECTORY;
41 public static String LOCK_FILE_NAME = "fmil.lock";
43 public static Object syncObject = new Object();
46 * Static: load native libraries required for the FMU simulation to work.
50 File[] libraries = new File[2];
52 ExecEnvironment env = ExecEnvironment.calculate();
55 URL sharedFMILIBUrl = null;
56 URL simulatorFMIUrl = null;
57 Bundle b = Platform.getBundle("org.simantics.fmil.core");
59 if (env.os == OSType.WINDOWS) {
60 sharedFMILIBUrl = FileLocator.find(b, new Path("libraries/fmilib_shared.dll"), null);
61 simulatorFMIUrl = FileLocator.find(b, new Path("libraries/FMUSimulator.dll"), null);
62 } else if(env.os == OSType.LINUX) {
63 sharedFMILIBUrl = FileLocator.find(b, new Path("libraries/libfmilib_shared.so"), null);
64 simulatorFMIUrl = FileLocator.find(b, new Path("libraries/libFMUSimulator.so"), null);
67 libraries[0] = new File(FileLocator.toFileURL(sharedFMILIBUrl).getPath());
68 libraries[1] = new File(FileLocator.toFileURL(simulatorFMIUrl).getPath());
69 } catch (Exception e) {
70 LOGGER.error("Failed to resolve native FMU simulation library for execution environment {}.{}", env.os, env.arch, e);
73 for(File library : libraries) {
75 System.err.println("FMU library not loaded. FMU simulation not working.");
77 } else if(!library.isFile()) {
78 System.err.println(library.getAbsolutePath() + " not found");
81 System.load(library.getAbsolutePath());
82 } catch (Throwable t) {
83 System.err.println(t.getMessage());
90 * Static: initialize fmu temp folder from current working directory
93 TEMP_FMU_COMMON_DIRECTORY = Paths.get(".").toAbsolutePath().normalize().toString();
96 public static void setTempFMUCommonDir(File dir) {
97 TEMP_FMU_COMMON_DIRECTORY = dir.getAbsolutePath();
100 private String fmuDir;
103 public String TEMP_FOLDER_1;
104 public String TEMP_FOLDER_2;
105 public String TEMP_FMU_DIRECTORY;
106 private String dirName;
108 private String[] variableNames;
109 private String[] variableDescriptions;
110 private String[] variableDeclaredTypes;
111 private int[] variableReferences;
112 private int[] variableTypes;
113 private int[] variableCausalities;
114 private int[] variableVariabilities;
116 private String[] declaredTypes;
117 private String[] declaredTypeDescriptions;
118 private String[] declaredTypeQuantities;
119 private String[] declaredTypeUnits;
121 private TObjectIntHashMap<String> variableMap = new TObjectIntHashMap<String>();
123 private Set<String> subscriptionSet = new HashSet<String>();
124 private TIntArrayList subscription = new TIntArrayList();
125 private ArrayList<String> subscribedNames = new ArrayList<String>();
127 public List<String> getSubscribedNames() {
128 return subscribedNames;
131 public boolean subscribe(String name) throws FMILException {
133 int vr = variableMap.get(name);
134 if(vr == 0) return false;
135 if(!subscriptionSet.add(name)) return false;
136 subscribedNames.add(name);
137 System.err.println("subscribed : " + name + " => " + subscribedNames.size());
138 subscription.add(vr);
139 subscribe(new int[] { vr });
144 // Create a directory for this control
145 File tempDir = new File(TEMP_FMU_COMMON_DIRECTORY, UUID.randomUUID().toString());
147 TEMP_FMU_DIRECTORY = tempDir.getAbsolutePath();
149 // Create two directories inside the temp directory for this control
150 dirName = UUID.randomUUID().toString();
151 File fmuDir = new File(TEMP_FMU_DIRECTORY, dirName);
154 TEMP_FOLDER_1 = fmuDir.toString();
155 TEMP_FOLDER_2 = fmuDir.toString() + "_2";
157 // Lock fmu directory in temp directory
161 public int getModelIDNew() {
165 public String getModelID() {
169 public String getFmuDir() {
174 * Load fmu from a given file path. Releases the (possible) previously
177 * @param path absolute file path for fmu file
178 * @throws FMILException
180 private int fmuN = 0;
181 private boolean fmuLoaded = false;
182 public void loadFMUFile(String path) throws FMILException {
184 synchronized(syncObject) {
187 fmuDir = TEMP_FOLDER_1;
190 fmuDir = TEMP_FOLDER_2;
194 File tempDir = new File(fmuDir);
195 if(tempDir.isDirectory()) {
197 FileUtils.deleteAll(tempDir);
198 } catch (IOException e) {
199 throw new FMILException("Could not create temp folder for fmu");
208 String tmpPath = tempDir.getAbsolutePath();
209 if(!tmpPath.endsWith("\\") && !tmpPath.endsWith("/"))
210 tmpPath = tmpPath + "/";
211 id = loadFMUFile_(path, tmpPath);
214 getAllVariableReferences();
216 for(int i=0;i<variableNames.length;i++) {
217 variableMap.put(variableNames[i], variableReferences[i]);
221 } catch (UnsatisfiedLinkError err) {
222 throw new FMILException(UNSATISFIED_LINK, err);
223 } catch (Exception e) {
224 LOGGER.error(e.getMessage());
225 throw new FMILException(e.getMessage());
230 private native int loadFMUFile_(String path, String toDir);
233 * Set a step length for simulation
235 * @param step Step length for simulation
236 * @throws FMILException
238 public void setStepLength(double step) throws FMILException {
239 synchronized(syncObject) {
242 int ret = setStepLength_(getModelIDNew(), step);
244 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
246 } catch (UnsatisfiedLinkError err) {
247 throw new FMILException(UNSATISFIED_LINK);
248 } catch (Exception e) {
249 throw new FMILException(e.getMessage());
254 private native int setStepLength_(int id, double step);
257 * Instantiates a simulation.
259 * Make sure that an FMU is loaded first.
260 * @throws FMILException
262 public void instantiateSimulation() throws FMILException {
263 synchronized(syncObject) {
267 int ret = instantiateSimulation_(getModelIDNew());
269 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
271 } catch (FMILException e) {
273 } catch (UnsatisfiedLinkError err) {
274 throw new FMILException(UNSATISFIED_LINK);
275 } catch (Exception e) {
276 throw new FMILException(e.getMessage());
281 private native int instantiateSimulation_(int id) throws FMILException;
285 * Initializes a simulation.
287 * Make sure that simulation is instantiated first!
288 * @throws FMILException
290 public void initializeSimulation() throws FMILException {
291 synchronized(syncObject) {
295 int ret = initializeSimulation_(getModelIDNew());
297 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
299 } catch (FMILException e) {
301 } catch (UnsatisfiedLinkError err) {
302 throw new FMILException(UNSATISFIED_LINK);
303 } catch (Exception e) {
304 throw new FMILException(e.getMessage());
309 private native int initializeSimulation_(int id) throws FMILException;
312 * Subscribe a set of variables from a loaded simulation.
314 * Make sure that an FMU is loaded first.
315 * @param variables Array of variables
316 * @throws FMILException
318 public void subscribe(int[] variables) throws FMILException {
319 synchronized(syncObject) {
323 int ret = subscribe_(getModelIDNew(), variables);
325 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
327 } catch (UnsatisfiedLinkError err) {
328 throw new FMILException(UNSATISFIED_LINK);
329 } catch (Exception e) {
330 throw new FMILException(e.getMessage());
335 private native int subscribe_(int id, int[] variables);
338 * Set a new (Real, double) value for a variable. If the variable is a
339 * parameter, the change is effective immediately.
341 * @param name Variable
342 * @param value New (Real, double) value
343 * @throws FMILException
345 public void setRealValue(String name, double value) throws FMILException {
347 synchronized(syncObject) {
351 int ret = setRealValue_(getModelIDNew(), variableMap.get(name), value);
353 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
355 } catch (FMILException e) {
357 } catch (UnsatisfiedLinkError err) {
358 throw new FMILException(UNSATISFIED_LINK);
359 } catch (Exception e) {
360 throw new FMILException(e.getMessage());
367 public void setRealValue(int variableReference, double value) throws FMILException {
369 synchronized(syncObject) {
373 int ret = setRealValue_(getModelIDNew(), variableReference, value);
375 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
377 } catch (FMILException e) {
379 } catch (UnsatisfiedLinkError err) {
380 throw new FMILException(UNSATISFIED_LINK);
381 } catch (Exception e) {
382 throw new FMILException(e.getMessage());
389 private native int setRealValue_(int id, int variableReference, double value) throws FMILException;
392 // * Set a new (integer) value for a variable. If the variable is a
393 // * parameter, the change is effective immediately.
395 // * @param name Variable
396 // * @param value New (integer) value
397 // * @throws FMILException
399 // public void setIntegerValue(String name, int value) throws FMILException {
400 // synchronized(syncObject) {
404 // int ret = setIntegerValue_(getModelID(), name, value);
406 // throw new FMILException(getLastErrorMessage());
408 // } catch (UnsatisfiedLinkError err) {
409 // throw new FMILException(UNSATISFIED_LINK);
410 // } catch (Exception e) {
411 // throw new FMILException(e.getMessage());
415 // private native int setIntegerValue_(String id, String name, int value);
418 // * Set a new (boolean) value for a variable. If the variable is a
419 // * parameter, the change is effective immediately.
421 // * @param name Variable
422 // * @param value New (boolean) value
423 // * @throws FMILException
425 // public void setBooleanValue(String name, boolean value) throws FMILException {
426 // synchronized(syncObject) {
430 // int ret = setBooleanValue_(getModelID(), name, value);
432 // throw new FMILException(getLastErrorMessage());
434 // } catch (UnsatisfiedLinkError err) {
435 // throw new FMILException(UNSATISFIED_LINK);
436 // } catch (Exception e) {
437 // throw new FMILException(e.getMessage());
441 // private native int setBooleanValue_(String id, String name, boolean value);
443 // public void setTime(double time) throws FMILException {
444 // synchronized(syncObject) {
448 // int ret = setTime_(getModelID(), time);
450 // throw new FMILException(getLastErrorMessage());
452 // } catch (UnsatisfiedLinkError err) {
453 // throw new FMILException(UNSATISFIED_LINK);
454 // } catch (Exception e) {
455 // throw new FMILException(e.getMessage());
459 // private native int setTime_(String id, double time);
462 * Simulate one step forward. The step length can be set with
465 * @throws FMILException
467 public void simulateStep() throws FMILException {
468 synchronized(syncObject) {
472 int ret = simulateStep_(getModelIDNew());
474 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
476 } catch (FMILException e) {
478 } catch (UnsatisfiedLinkError err) {
479 throw new FMILException(UNSATISFIED_LINK);
480 } catch (Exception e) {
481 throw new FMILException(e.getMessage());
485 private native int simulateStep_(int id) throws FMILException;
488 * Get an array containing the current values for subscribed variables. The
489 * values are in the same order as in the subscription.
491 * @param results An array the size of subscribed results
494 public double[] getSubscribedResults() throws FMILException {
495 synchronized(syncObject) {
499 double[] results = new double[subscription.size()];
500 return getSubscribedResults_(getModelIDNew(), results);
502 } catch (UnsatisfiedLinkError err) {
503 throw new FMILException(UNSATISFIED_LINK);
504 } catch (Exception e) {
505 throw new FMILException(e.getMessage());
510 private native double[] getSubscribedResults_(int id, double[] results);
514 * Unload FMU and the dll:s that it requires.
516 * To be called after all FMU simulations are ended.
517 * If the fmu is loaded again / changed, call to loadFMUFile is sufficient. loadFMUFile
518 * releases the previous fmu.dll
520 * @throws FMILException
522 public void unloadFMU() throws FMILException {
523 synchronized(syncObject) {
527 unlockFMUDirectory();
529 int ret = unloadFMU_(getModelIDNew());
531 LOGGER.warn("Function return value != OK, an exception should have been thrown from native code!");
534 removeFMUDirectoryContents();
536 } catch (FMILException e) {
538 } catch (UnsatisfiedLinkError err) {
539 throw new FMILException(UNSATISFIED_LINK);
540 } catch (Exception e) {
541 throw new FMILException(e.getMessage());
545 private native int unloadFMU_(int id) throws FMILException;
548 // * Checks if fmu has been initialized
549 // * @return current simulation time
551 // public boolean isInitialized() throws FMILException {
552 // synchronized(syncObject) {
554 // return isInitialized_(getModelID());
555 // } catch (UnsatisfiedLinkError err) {
556 // throw new FMILException(UNSATISFIED_LINK);
557 // } catch (Exception e) {
558 // throw new FMILException(e.getMessage());
563 // private native boolean isInitialized_(String id);
566 * Get the current simulation time
567 * @return current simulation time
569 public double getTime() throws FMILException {
570 synchronized(syncObject) {
574 return getTime_(getModelIDNew());
576 } catch (UnsatisfiedLinkError err) {
577 throw new FMILException(UNSATISFIED_LINK);
578 } catch (Exception e) {
579 throw new FMILException(e.getMessage());
584 private native double getTime_(int id);
587 * Get all variables in a loaded model
588 * @return all variables in a loaded model
590 public String[] getAllVariables() throws FMILException {
591 synchronized(syncObject) {
595 if(variableNames == null) {
596 variableNames = getAllVariables_(getModelIDNew());
598 return variableNames;
600 } catch (UnsatisfiedLinkError err) {
601 throw new FMILException(UNSATISFIED_LINK);
602 } catch (Exception e) {
603 throw new FMILException(e.getMessage());
608 private native String[] getAllVariables_(int id);
610 public String[] getAllVariableDescriptions() throws FMILException {
611 synchronized(syncObject) {
615 if(variableDescriptions == null) {
616 variableDescriptions = getAllVariableDescriptions_(getModelIDNew());
618 return variableDescriptions;
620 } catch (UnsatisfiedLinkError err) {
621 throw new FMILException(UNSATISFIED_LINK);
622 } catch (Exception e) {
623 throw new FMILException(e.getMessage());
628 private native String[] getAllVariableDescriptions_(int id);
630 public String[] getAllVariableDeclaredTypes() throws FMILException {
631 synchronized(syncObject) {
635 if(variableDeclaredTypes == null) {
636 variableDeclaredTypes = getAllVariableDeclaredTypes_(getModelIDNew());
638 return variableDeclaredTypes;
640 } catch (UnsatisfiedLinkError err) {
641 throw new FMILException(UNSATISFIED_LINK);
642 } catch (Exception e) {
643 throw new FMILException(e.getMessage());
648 private native String[] getAllVariableDeclaredTypes_(int id);
650 public int[] getAllVariableReferences() throws FMILException {
651 synchronized(syncObject) {
655 if(variableReferences == null) {
656 variableReferences = getAllVariableReferences_(getModelIDNew(), new int[variableNames.length]);
658 return variableReferences;
660 } catch (UnsatisfiedLinkError err) {
661 throw new FMILException(UNSATISFIED_LINK);
662 } catch (Exception e) {
663 throw new FMILException(e.getMessage());
668 private native int[] getAllVariableReferences_(int id, int[] array);
670 public int[] getAllVariableTypes() throws FMILException {
671 synchronized(syncObject) {
675 if(variableTypes == null) {
676 variableTypes = getAllVariableTypes_(getModelIDNew(), new int[variableNames.length]);
678 return variableTypes;
680 } catch (UnsatisfiedLinkError err) {
681 throw new FMILException(UNSATISFIED_LINK);
682 } catch (Exception e) {
683 throw new FMILException(e.getMessage());
688 private native int[] getAllVariableTypes_(int id, int[] array);
690 public int[] getAllVariableCausalities() throws FMILException {
691 synchronized(syncObject) {
695 if(variableCausalities == null) {
696 variableCausalities = getAllVariableCausalities_(getModelIDNew(), new int[variableNames.length]);
698 return variableCausalities;
700 } catch (UnsatisfiedLinkError err) {
701 throw new FMILException(UNSATISFIED_LINK);
702 } catch (Exception e) {
703 throw new FMILException(e.getMessage());
708 private native int[] getAllVariableCausalities_(int id, int[] array);
710 public int[] getAllVariableVariabilities() throws FMILException {
711 synchronized(syncObject) {
715 if(variableVariabilities == null) {
716 variableVariabilities = getAllVariableVariabilities_(getModelIDNew(), new int[variableNames.length]);
718 return variableVariabilities;
720 } catch (UnsatisfiedLinkError err) {
721 throw new FMILException(UNSATISFIED_LINK);
722 } catch (Exception e) {
723 throw new FMILException(e.getMessage());
728 private native int[] getAllVariableVariabilities_(int id, int[] array);
731 * Get all variables in a loaded model
732 * @return all variables in a loaded model
734 public String[] getAllDeclaredTypes() throws FMILException {
735 synchronized(syncObject) {
739 if(declaredTypes == null) {
740 declaredTypes = getAllDeclaredTypes_(getModelIDNew());
742 return declaredTypes;
744 } catch (UnsatisfiedLinkError err) {
745 throw new FMILException(UNSATISFIED_LINK);
746 } catch (Exception e) {
747 throw new FMILException(e.getMessage());
752 private native String[] getAllDeclaredTypes_(int id);
754 public String[] getAllDeclaredTypeDescriptions() throws FMILException {
755 synchronized(syncObject) {
759 if(declaredTypeDescriptions == null) {
760 declaredTypeDescriptions = getAllDeclaredTypeDescriptions_(getModelIDNew());
762 return declaredTypeDescriptions;
764 } catch (UnsatisfiedLinkError err) {
765 throw new FMILException(UNSATISFIED_LINK);
766 } catch (Exception e) {
767 throw new FMILException(e.getMessage());
772 private native String[] getAllDeclaredTypeDescriptions_(int id);
774 public String[] getAllDeclaredTypeQuantities() throws FMILException {
775 synchronized(syncObject) {
779 if(declaredTypeQuantities == null) {
780 declaredTypeQuantities = getAllDeclaredTypeQuantities_(getModelIDNew());
782 return declaredTypeQuantities;
784 } catch (UnsatisfiedLinkError err) {
785 throw new FMILException(UNSATISFIED_LINK);
786 } catch (Exception e) {
787 throw new FMILException(e.getMessage());
792 private native String[] getAllDeclaredTypeQuantities_(int id);
794 public String[] getAllDeclaredTypeUnits() throws FMILException {
795 synchronized(syncObject) {
799 if(declaredTypeUnits == null) {
800 declaredTypeUnits = getAllDeclaredTypeUnits_(getModelIDNew());
802 return declaredTypeUnits;
804 } catch (UnsatisfiedLinkError err) {
805 throw new FMILException(UNSATISFIED_LINK);
806 } catch (Exception e) {
807 throw new FMILException(e.getMessage());
812 private native String[] getAllDeclaredTypeUnits_(int id);
817 // * Get all variables from model that match the filter (and time variable)
819 // * @param regexp Regular expression filter
820 // * @return An array of variable names that match regexp filter (and time-variable)
821 // * @throws FMILException
823 // public String[] filterVariables(String regexp) throws FMILException {
824 // synchronized(syncObject) {
827 // return filterVariables_(getModelID(), regexp + "|time");
829 // } catch (UnsatisfiedLinkError err) {
830 // throw new FMILException(UNSATISFIED_LINK);
831 // } catch (Exception e) {
832 // throw new FMILException(e.getMessage());
837 // private native String[] filterVariables_(String id, String regexp);
840 // * Get the last error message
841 // * @return Last error message
843 // public String getLastErrorMessage() throws FMILException {
844 // synchronized(syncObject) {
849 // //return getLastErrorMessage_(getModelID());
851 // } catch (UnsatisfiedLinkError err) {
852 // throw new FMILException(UNSATISFIED_LINK);
853 // } catch (Exception e) {
854 // throw new FMILException(e.getMessage());
859 // private native String getLastErrorMessage_(String id);
862 * Get a real (double) value for variable
863 * @param name Name of the variable
865 * @throws FMILException
867 public double getRealValue(String name) throws FMILException {
868 synchronized(syncObject) {
871 // TODO: printtaa id ja name, jotta saadaan virheessä kiinni
872 double result = getRealValue_(getModelIDNew(), variableMap.get(name));
873 System.err.println("getRealValue " + name + " = " + result);
875 } catch (UnsatisfiedLinkError err) {
876 throw new FMILException(UNSATISFIED_LINK);
877 } catch (Exception e) {
878 throw new FMILException(e.getMessage());
883 private native double getRealValue_(int id, int variableReference);
886 // * Get a string value for variable
887 // * @param name Name of the variable
889 // * @throws FMILException
891 // public String getStringValue(String name) throws FMILException {
892 // synchronized(syncObject) {
895 // return getStringValue_(getModelID(), name);
896 // } catch (UnsatisfiedLinkError err) {
897 // throw new FMILException(UNSATISFIED_LINK);
898 // } catch (Exception e) {
899 // throw new FMILException(e.getMessage());
904 // private native String getStringValue_(String id, String name);
907 // * Get an integer value for variable
908 // * @param name Name of the variable
910 // * @throws FMILException
912 // public int getIntegerValue(String name) throws FMILException {
913 // synchronized(syncObject) {
916 // return getIntegerValue_(getModelID(), name);
917 // } catch (UnsatisfiedLinkError err) {
918 // throw new FMILException(UNSATISFIED_LINK);
919 // } catch (Exception e) {
920 // throw new FMILException(e.getMessage());
925 // private native int getIntegerValue_(String id, String name);
928 // * Get a real (double) value for variable
929 // * @param name Name of the variable
931 // * @throws FMILException
933 // public boolean getBooleanValue(String name) throws FMILException {
934 // synchronized(syncObject) {
937 // return getBooleanValue_(getModelID(), name);
938 // } catch (UnsatisfiedLinkError err) {
939 // throw new FMILException(UNSATISFIED_LINK);
940 // } catch (Exception e) {
941 // throw new FMILException(e.getMessage());
946 // private native boolean getBooleanValue_(String id, String name);
948 private FileChannel channel;
949 private FileLock lock;
951 @SuppressWarnings("resource")
952 private boolean lockFMUDirectory() {
955 // Get a file channel for the lock file
956 File lockFile = new File(TEMP_FMU_DIRECTORY, LOCK_FILE_NAME);
957 if(!lockFile.isFile())
958 lockFile.createNewFile();
960 channel = new RandomAccessFile(lockFile, "rw").getChannel();
962 // Use the file channel to create a lock on the file.
963 // This method blocks until it can retrieve the lock.
964 lock = channel.lock();
966 // // Try acquiring the lock without blocking. This method returns
967 // // null or throws an exception if the file is already locked.
969 // lock = channel.tryLock();
970 // } catch (OverlappingFileLockException e) {
971 // // File is already locked in this thread or virtual machine
973 } catch (IOException e) {
980 private boolean unlockFMUDirectory() {
989 } catch (IOException e) {
995 private boolean removeFMUDirectoryContents() {
998 File tempDir = new File(TEMP_FMU_DIRECTORY);
999 FileUtils.deleteAll(tempDir);
1001 } catch (IOException e) {
1008 protected void finalize() throws Throwable {
1011 } catch (Throwable t) {
1012 LOGGER.error("Could not unload native FMU!", t);