Looking from an oblique angle, React solves a peculiar problem: how to make a static markup language act dynamic. Judging by React's popularity, most of us would agree that React does a particularly good job solving this problem, and code using this ideology of component rendering often leads to self-contained reusable modules.
As an ex-web developer, I always prefer React over any other framework. However, the general design patterns have a much broader use case than solely web development.
The Problem I was Facing
For quite an extensive amount of time, I have been working with Deepmind's simulation software Multi-Joint dynamics with Contact, or MuJoCo for short. For our argument, it does not matter too much what exactly MuJoCo is. We will only focus on its scene definition. By design, simulated environments are compiled from XML files, which define the layout of the simulated scene and its objects.
Just to have an idea, you can find below a minimal example with two cubes, a sphere, and a ground plane.
<mujoco model="simple_scene">
<worldbody>
<!-- Ground plane -->
<geom name="ground" type="plane" size="10 10 0.1"/>
<!-- Red box with 6DOF joint -->
<body name="box_one" pos="0 0 0.1">
<freejoint />
<geom name="box_geom" type="box" size="0.1 0.1 0.1"/>
</body>
<!-- Blue box with 6DOF joint -->
<body name="box_two" pos="0 0 0.5">
<freejoint />
<geom name="box_geom" type="box" size="0.1 0.1 0.1"/>
</body>
<!-- Small sphere -->
<body name="small_sphere" pos="0 0.5 0.1">
<geom name="sphere_geom" type="sphere" size="0.05"/>
</body>
<!-- would be nice to add other objects -->
</worldbody>
</mujoco>
This scene is particularly boring as not much will happen, but the parallel to HTML should be quite obvious. The problem arises when one wants something more dynamic with a variable number of objects. In my case, I had to dynamically exchange robot grippers and random objects throughout various scenes.
So, my goal was to recreate the basics of React for better handling.
It turned out much simpler than I previously imagined.
The Basics of React
At a high level, React provides a simple framework with a custom syntax called JSX to specify components. Components are self-contained units that maintain their own logic and markup. Components can also consist of other components, thus creating a hierarchy.
This creates a tree structure that mimics the actual Document Object Model (DOM) in the browser. To generate the final HTML, React uses a “render” method on the root component, which recursively calls the “render” method on all nested components.
Of course, modern React is much more complex than this, but this covers the basics and is all we need.
Mimicking React in Python
MuJoCo is written in C/C++ and provides native Python bindings for quick prototyping. As I did not plan to recreate JSX inside of Python, I simply opted for the multiline string using placeholders.
In the example MuJoCo scene above, this would translate to:
XML = """
<mujoco model="simple_scene">
<worldbody>
{objects}
</worldbody>
</mujoco>
"""
Fortunately, Python comes with a string formatting convention, allowing us to use named placeholders ({objects}
in this example) for future replacement using the format(object="string for replacement")
method call on the template string.
This looks promising.
Similarly, we can create an XML template for the objects. Here is an example template string that allows for custom size settings for a box object:
XML = """
<body name="{name}" pos="0 0 0.1">
<freejoint />
<geom name="box_geom" type="box" size="{size}"/>
</body>
"""
Next, we need an interface for each component to make them share a method required for rendering each component:
from abc import ABC, abstractmethod
class MjXML(ABC):
@abstractmethod
def to_xml(self) -> str:
pass
In the final XML assembly step, we will call on each component the to_xml
method, which will output the resulting string for each component that will be used in the template.
Next, we implement the corresponding component for the example object above:
XML = """
<body name="{name}" pos="0 0 0.1">
<freejoint />
<geom name="box_geom" type="box" size="{size}"/>
</body>
"""
class SimpleBox(MjXML):
def __init__(self, name: str, size: float):
self.name = name
self.size = size
def to_xml(self):
box_xml = XML.format(name=self.name, size=self.size)
return box_xml
Finally, we implement the component for the scene:
XML = """
<mujoco model="simple_scene">
<worldbody>
{objects}
</worldbody>
</mujoco>
"""
class SimpleScene(MjXML):
def __init__(self, object_list: List[MjXML]):
self.object_list = object_list
def to_xml(self):
obj_xml = " ".join([obj.to_xml() for obj in self.object_list])
full_xml = XML.format(objects=obj_xml)
return full_xml
Essentially, this is already a minimal workable example.
To actually simulate, we would need to construct the model and data of MuJoCo using our constructed XML string. As this article is not about MuJoCo, I have excluded the simulation part from this code sample.
Possible Extensions
While this example covers the basic concept, my specific use case required additional features such as dynamic loading of assets, randomizing names, and custom collision logic. Let me know, if you are interested in more complex physical simulations.
However, these additional features might overcomplicate the core message I want to convey: if you are working with static markup that needs dynamic behavior, consider using a component-based rendering mechanism similar to React. It can simplify development and enhance the flexibility of your applications.
Top comments (0)