Skip to content

Commit

Permalink
Allow multiples FDM instances to share the same property manager. (JS…
Browse files Browse the repository at this point in the history
  • Loading branch information
bcoconni committed Jan 2, 2023
1 parent e1855fc commit 3be58a7
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 18 deletions.
27 changes: 22 additions & 5 deletions python/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
from ._jsbsim import (__version__, FGAerodynamics, FGAircraft, FGAtmosphere, FGAuxiliary,
FGEngine, FGFDMExec, FGGroundReactions, FGJSBBase, FGLGear,
FGMassBalance, FGPropagate, FGPropertyManager,
FGPropulsion, BaseError, TrimFailureError,
get_default_root_dir, eTemperature, ePressure)
from ._jsbsim import (
__version__,
BaseError,
FGAerodynamics,
FGAircraft,
FGAtmosphere,
FGAuxiliary,
FGEngine,
FGFDMExec,
FGGroundReactions,
FGJSBBase,
FGLGear,
FGMassBalance,
FGPropagate,
FGPropertyManager,
FGPropertyNode,
FGPropulsion,
TrimFailureError,
ePressure,
eTemperature,
get_default_root_dir,
)
13 changes: 12 additions & 1 deletion python/jsbsim.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,21 @@ cdef extern from "initialization/FGInitialCondition.h" namespace "JSBSim":
c_FGInitialCondition(c_FGInitialCondition* ic)
bool Load(const c_SGPath& rstfile, bool useStoredPath)

cdef extern from "input_output/FGPropertyManager.h" namespace "JSBSim":
cdef cppclass c_FGPropertyNode "JSBSim::FGPropertyNode":
c_FGPropertyNode* GetNode(const string& path, bool create)
const string& GetName() const
const string& GetFullyQualifiedName() const
double getDoubleValue() const
void setDoubleValue(double value)

cdef extern from "input_output/FGPropertyManager.h" namespace "JSBSim":
cdef cppclass c_FGPropertyManager "JSBSim::FGPropertyManager":
c_FGPropertyManager()
bool HasNode(string path) except +convertJSBSimToPyExc
c_FGPropertyManager(c_FGPropertyNode* root)
c_FGPropertyNode* GetNode()
c_FGPropertyNode* GetNode(const string& path, bool create)
bool HasNode(const string& path) except +convertJSBSimToPyExc

cdef extern from "math/FGColumnVector3.h" namespace "JSBSim":
cdef cppclass c_FGColumnVector3 "JSBSim::FGColumnVector3":
Expand Down
89 changes: 82 additions & 7 deletions python/jsbsim.pyx.in
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

@DoxMainPage"""

from typing import Optional

import enum
import errno
import os
Expand Down Expand Up @@ -111,21 +113,84 @@ cdef class FGPropagate:
return _convertToNumpyVec(self.thisptr.GetUVW())


cdef class FGPropertyNode:
"""@Dox(JSBSim::FGPropertyNode)"""

cdef c_FGPropertyNode* thisptr

def __cinit__(self, *args, **kwargs):
self.thisptr = NULL

def __bool__(self) -> bool:
"""Check if the object is initialized."""
return self.thisptr is not NULL

def __str__(self) -> str:
if self.thisptr is not NULL:
return f"Property '{self.get_fully_qualified_name()}' (value: {self.get_double_value()})"
return "Uninitialized property"

cdef __intercept_invalid_pointer(self):
if self.thisptr is NULL:
raise BaseError("Object is not initialized")

cdef __validate_node_pointer(self, create: bool):
if self.thisptr is not NULL:
return self
else:
if create:
raise MemoryError()
return None

def get_name(self) -> str:
"""@Dox(JSBSim::FGPropertyManager::GetName)"""
self.__intercept_invalid_pointer()
return self.thisptr.GetName().decode()

def get_fully_qualified_name(self) -> str:
"""@Dox(JSBSim::FGPropertyManager::GetFullyQualifiedName)"""
self.__intercept_invalid_pointer()
return self.thisptr.GetFullyQualifiedName().decode()

def get_node(self, path: str, create: bool = False) -> Optional[FGPropertyNode]:
self.__intercept_invalid_pointer()
node = FGPropertyNode()
node.thisptr = self.thisptr.GetNode(path.encode(), create)
return node.__validate_node_pointer(create)

def get_double_value(self) -> float:
self.__intercept_invalid_pointer()
return self.thisptr.getDoubleValue()

def set_double_value(self, value: float) -> None:
self.__intercept_invalid_pointer()
self.thisptr.setDoubleValue(value)

cdef class FGPropertyManager:
"""@Dox(JSBSim::FGPropertyManager)"""

cdef c_FGPropertyManager *thisptr
cdef bool thisptr_owner

def __cinit__(self, new_instance=False, *args, **kwargs):
if new_instance:
self.thisptr = new c_FGPropertyManager()
def __cinit__(self, FGPropertyNode node = None, new_instance=True, *args, **kwargs):
if node is None:
if new_instance:
self.thisptr = new c_FGPropertyManager()
if self.thisptr is NULL:
raise MemoryError()
self.thisptr_owner = True
else:
self.thisptr = NULL
self.thisptr_owner = False
else:
try:
node.__intercept_invalid_pointer()
except BaseError:
raise BaseError("Cannot instantiate FGPropertyManager with an uninitialized property node.")
self.thisptr = new c_FGPropertyManager(node.thisptr)
if self.thisptr is NULL:
raise MemoryError()
self.thisptr_owner = True
else:
self.thisptr = NULL
self.thisptr_owner = False

def __dealloc__(self) -> None:
if self.thisptr is not NULL and self.thisptr_owner:
Expand All @@ -141,6 +206,15 @@ cdef class FGPropertyManager:
if self.thisptr is NULL:
raise BaseError("Object is not initialized")

def get_node(self, path: Optional[str] = None, create: bool = False) -> Optional[FGPropertyNode]:
"""@Dox(JSBSim::FGPropertyManager::GetNode)"""
node = FGPropertyNode()
if path is None:
node.thisptr = self.thisptr.GetNode()
else:
node.thisptr = self.thisptr.GetNode(path.encode(), create)
return node.__validate_node_pointer(create)

def hasNode(self, path: str) -> bool:
"""@Dox(JSBSim::FGPropertyManager::HasNode)"""
self.__intercept_invalid_pointer()
Expand Down Expand Up @@ -469,6 +543,7 @@ cdef class FGFDMExec(FGJSBBase):

if pm_root:
root = pm_root.thisptr
pm_root.thisptr_owner = False
else:
root = NULL

Expand Down Expand Up @@ -745,7 +820,7 @@ cdef class FGFDMExec(FGJSBBase):

def get_property_manager(self) -> FGPropertyManager:
"""@Dox(JSBSim::FGFDMExec::GetPropertyManager)"""
pm = FGPropertyManager()
pm = FGPropertyManager(None, new_instance=False)
pm.thisptr = self.thisptr.GetPropertyManager()
return pm

Expand Down
2 changes: 1 addition & 1 deletion src/FGFDMExec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ FGFDMExec::FGFDMExec(FGPropertyManager* root, unsigned int* fdmctr)
// Prepare FDMctr for the next child FDM id
(*FDMctr)++; // instance. "child" instances are loaded last.

FGPropertyNode* instanceRoot = Root->GetNode("/fdm/jsbsim",IdFDM,true);
FGPropertyNode* instanceRoot = Root->GetNode("fdm/jsbsim",IdFDM,true);
instance = new FGPropertyManager(instanceRoot);

try {
Expand Down
80 changes: 76 additions & 4 deletions tests/TestMiscellaneous.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,16 @@

import os

from JSBSim_utils import JSBSimTestCase, RunTest, jsbsim
from JSBSim_utils import CreateFDM, JSBSimTestCase, RunTest, jsbsim


class TestMiscellaneous(JSBSimTestCase):
def test_property_access(self):
fdm = self.create_fdm()
fdm.load_model("ball")
fdm.run_ic()

# Check that the node 'qwerty' does not exist
pm = fdm.get_property_manager()
self.assertEqual(pm.get_node().get_fully_qualified_name(), "/fdm/jsbsim")
self.assertFalse(pm.hasNode("qwerty"))

# Check the default behavior of get_property_value. Non existing
Expand All @@ -49,6 +48,40 @@ def test_property_access(self):
self.assertAlmostEqual(fdm.get_property_value("qwerty"), 42.0)
self.assertAlmostEqual(fdm["qwerty"], 42.0)

# Test the FGPropertyNode API to access property nodes
node = pm.get_node("qwerty")
if not node:
self.fail()
self.assertAlmostEqual(node.get_double_value(), 42.0)
node.set_double_value(-1.0)
self.assertAlmostEqual(node.get_double_value(), -1.0)
self.assertAlmostEqual(fdm.get_property_value("qwerty"), -1.0)
self.assertAlmostEqual(fdm["qwerty"], -1.0)

# Test the FGPropertyNode to create new nodes
self.assertFalse(pm.hasNode("egg"))
self.assertIsNone(pm.get_node("egg")) # `create` flag set to default (False)
self.assertIsNone(pm.get_node("egg", False)) # `create` flag set to False
egg_node = pm.get_node("egg", True) # `create` flag set to True
self.assertEqual(egg_node.get_name(), "egg")
self.assertEqual(egg_node.get_fully_qualified_name(), "/fdm/jsbsim/egg")

egg_node.set_double_value(0.25)
self.assertAlmostEqual(egg_node.get_double_value(), 0.25)
self.assertAlmostEqual(fdm.get_property_value("egg"), 0.25)
self.assertAlmostEqual(fdm["egg"], 0.25)

# Create a sub-node from a node
self.assertIsNone(egg_node.get_node("spam"))
spam_node = egg_node.get_node("spam", True)
self.assertEqual(spam_node.get_name(), "spam")
self.assertEqual(spam_node.get_fully_qualified_name(), "/fdm/jsbsim/egg/spam")

pm2 = jsbsim.FGPropertyManager()
root_node = pm2.get_node()
self.assertEqual(root_node.get_name(), "")
self.assertEqual(root_node.get_fully_qualified_name(), "/")

def test_property_catalog(self):
fdm = self.create_fdm()
fdm.load_model("ball")
Expand All @@ -68,7 +101,7 @@ def test_FG_reset(self):
# This test reproduces how FlightGear resets. The important thing is
# that the property manager is managed by FlightGear. So it is not
# deleted when the JSBSim instance is killed.
pm = jsbsim.FGPropertyManager(new_instance=True)
pm = jsbsim.FGPropertyManager()

self.assertFalse(pm.hasNode("fdm/jsbsim/ic/lat-geod-deg"))

Expand Down Expand Up @@ -172,5 +205,44 @@ def test_invalid_pointer_wont_crash(self):
if not fdm.get_propulsion():
self.fail()

node = jsbsim.FGPropertyNode()
if node:
self.fail()
with self.assertRaises(jsbsim.BaseError):
node.get_name()
with self.assertRaises(jsbsim.BaseError):
node.get_fully_qualified_name()
with self.assertRaises(jsbsim.BaseError):
node.get_node("x", False)
with self.assertRaises(jsbsim.BaseError):
node.get_node("x", True)
with self.assertRaises(jsbsim.BaseError):
node.get_double_value()
with self.assertRaises(jsbsim.BaseError):
node.set_double_value(1.0)

pm = jsbsim.FGPropertyManager()
node = pm.get_node()
if not node:
self.fail()

def test_property_manager_sharing(self):
script_path = self.sandbox.path_to_jsbsim_file("scripts")
pm = jsbsim.FGPropertyManager()
root_c172 = pm.get_node("/c172", True)

fdm_c172 = CreateFDM(self.sandbox, jsbsim.FGPropertyManager(root_c172))
fdm_c172.load_script(os.path.join(script_path, "c1721.xml"))
fdm_c172.run_ic()

root_S23 = pm.get_node("/Short_S23", True)
fdm_S23 = CreateFDM(self.sandbox, jsbsim.FGPropertyManager(root_S23))
fdm_S23.load_script(os.path.join(script_path, "Short_S23_1.xml"))
fdm_S23.run_ic()

while fdm_c172["simulation/sim-time-sec"] < 1.0:
fdm_c172.run()
self.assertEqual(fdm_S23["simulation/sim-time-sec"], 0.0)


RunTest(TestMiscellaneous)

0 comments on commit 3be58a7

Please sign in to comment.