4 Hugues Ross - Blog: Halberd: That's my property!
Hugues Ross

12/25/18

Halberd: That's my property!

To anyone reading this on the same day that I post, Merry Christmas! Here's a little present: This year's final Halberd-related blog post.

Of my current priorities on Halberd, one of the biggest is building and integrating a property grid. I think this is a very interesting endeavor, and hopefully you'll see it the same way!

So what's a Property Grid?

When I spoke to some (programmer) friends about this, I was surprised to find out that some of them didn't initially know what a property grid was. So, let's start with definitions.

As the name implies, a property grid is a ui widget that lets the user inspect and edit the properties of an object. It usually amounts to a list of named fields, and you can find one in just about any game engine. Here are a few examples:

UE4's 'details panel' is a form of property grid
As expected, Unity's inspector panel is another good example
The ActiPro Windows UI framework also features a property grid
The GTK UI builder, Glade, also features something in this vein

If you're a game developer, you've probably figured out by now that property grids are a really important game development tool. This is even more true in RPGs, due to the large amounts of 'database' assets which lend themselves well to this type of UI pattern.

Digging deeper: How does a property grid work?

Like any programming problem, there are many ways to implement property grid functionality. However, I'd put most methods into one of 3 groups: Inspected, Declared, and Fixed. These are just personal categories, not proper terms, but I think they showcase some real differences.

An Inspected property grid assembles itself by looking at real game data and converting it to ui. This is typically done through Reflection, either by looking at data in a scripting layer or with a language that natively supports this, such as C#. For languages that can't do this out of the box, a dynamic class/property system can be added on top of the normal code. Examples of such systems can be found in Unreal Engine 4 and the GObject framework.

A Declared property grid is also dynamic. However, instead of inspecting objects at runtime it queries their information from a predefined source, such as a schema or similar descriptive document. Then, it sends data back and forth via Serialization instead of reflection. The result is less flexible, but it's also simpler and the underlying game code may be more performant since dynamic properties aren't necessary.

Finally, there is the Fixed property grid. I call this a property grid, but that may be an overstatement--this grid is essentially just a hard-coded bit of ui for interfacing with an object. By its nature, it's quite simple and totally static. Of course, it's also difficult to maintain and requires constant updates as the underlying data changes.

Depending on your use-case, any of the above can be valid approaches. The fixed solution may seem bad, but sometimes it's the best option: You don't need a powerful and complex solution just to edit a couple of fields.

What have I got?

At the moment, Halberd is stuck with the fixed style. I went for this solution early on to avoid getting bogged down while developing my proof-of-concept, but the scaling problems of this approach are starting to appear.

The general process of adding a new property to an object right now is:
  1. Add the property
  2. Add serialization for saving/loading
  3. Add simple getters/setters for editing
  4. Add the property to a Vapi file for interop access
  5. Add the property's editing widget to the ui file
  6. Add the widget code to the editor's vala class, including the signals for changing the property
That's a lot of steps for one property. With a simple property grid, I could skip steps 3-6 entirely and greatly streamline my development process.

A new property grid

With all that in mind, I decided to try out something better. Since most of my code is C, the declared style is necessary. But how to prepare the meta-information about the properties? In my case, it was actually rather simple. Since I already serialize my data to XML, I decided to go with the obvious option: XSD.

For the uninitiated, XSD is an XML-based schema standard for describing other XML files. While intended for databases, with a few additions the result can work quite well for defining a property grid!

The result looks like this:
You might recognize this as DFGame's editor demo which I completed last year. The controls were hard-coded since it was just a viewport showcase at the time, but now the editable parts are totally data-driven!

The schema that the editor is based on is a bit wordy, but pretty straightforward:
    1 <xs:schema
    2     xmlns:df="/org/df458/dfgame"
    3     xmlns:xs="http://www.w3.org/2001/XMLSchema"
    4     elementFormDefault="qualified">
    5     <!-- Types -->
    6     <xs:simpleType name="fill_type">
    7         <xs:restriction base="xs:string">
    8             <xs:enumeration value="true" df:displayName="Filled"/>
    9             <xs:enumeration value="false" df:displayName="Wireframe"/>
   10         </xs:restriction>
   11     </xs:simpleType>
   12     <xs:simpleType name="size_type">
   13         <xs:restriction base="xs:float">
   14             <xs:minInclusive value="0"/>
   15             <xs:totalDigits value="6"/>
   16         </xs:restriction>
   17         <xs:annotation>
   18             <xs:documentation>A decimal value above 0</xs:documentation>
   19         </xs:annotation>
   20     </xs:simpleType>
   21     <xs:simpleType name="degree_type">
   22         <xs:restriction base="xs:float">
   23             <xs:minInclusive value="0"/>
   24             <xs:maxExclusive value="360"/>
   25         </xs:restriction>
   26         <xs:annotation>
   27             <xs:documentation>An angle in degrees</xs:documentation>
   28         </xs:annotation>
   29     </xs:simpleType>
   30 
   31     <!-- Triangle struct -->
   32     <xs:complexType name="triangle">
   33         <xs:attribute name="size" type="size_type">
   34             <xs:annotation>
   35                 <xs:documentation>The size of the triangle</xs:documentation>
   36             </xs:annotation>
   37         </xs:attribute>
   38         <xs:attribute name="angle" type="degree_type">
   39             <xs:annotation>
   40                 <xs:documentation>The angle of rotation</xs:documentation>
   41             </xs:annotation>
   42         </xs:attribute>
   43         <xs:attribute name="color" type="df:color4">
   44             <xs:annotation>
   45                 <xs:documentation>The color of the triangle</xs:documentation>
   46             </xs:annotation>
   47         </xs:attribute>
   48         <xs:attribute name="is_filled" type="fill_type" df:displayName="Style">
   49             <xs:annotation>
   50                 <xs:documentation>Toggles filled/wireframe rendering</xs:documentation>
   51             </xs:annotation>
   52         </xs:attribute>
   53     </xs:complexType>
   54 </xs:schema>
One way to reduce the workload even further would be to tie this into a code generation system, but that strikes me as overkill for the time being...

What now?

I've been working on this feature for a few weeks now, but I finally merged it into DFGame's master branch yesterday. The next step will be to try and integrate it into Halberd, I'm sure there'll be more to do but I think this feature is a good investment for future work! Once the property grid's in place, the work will hopefully start moving a bit quicker.

No comments: