This is the second part of a series documenting the development of ‘ACFactory’ (ACFabrik in German), an application that generates printable character sheets for the Pen & Paper role playing game Arcane Codex (English page).
You might also want to read Day 1: SealedSun goes WPF.
ExtendableEnum
Talents in Arcane Codex have a number of properties that are best described as enumerations. Unfortunately plain old enumerations are very flat. Translating them (when displayed in a user interface) requires you to wrap each and every appearance in the system. This is why I need a richer enumeration type.
Enter ExtendableEnum, an abstract base class that handles comparison and parsing of enumeration values. An existing enumeration could even be extended by a plug-in, should my application ever implement a plug-in system.
I was, however, confronted with a very annoying problem: type initialisation is lazy. The CLR employs certain “heuristics” to find out when to initialise a type. By default, a type is marked with “beforeFieldInit”, which means that the type is usable before its static fields have been initialised. Of course, static fields are always initialised “just-in-time” when they are accessed. The attribute is not applied when the class contains a static constructor (or class constructor or type initializer). In that case, the type is initialised when one of its members is first accessed.
While this is a pretty good strategy for the “normal” use of types, it might be a problem in XAML-based applications since the ExtendableEnum-parser is used before any of the enumeration values is referenced in code, which in turn means that the corresponding enumeration types had no chance to register their enumeration values with the corresponding registry.
One Solution I have found is the System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor method which, well, runs type initializers. I can only hope that the method (which points right into the heart of the CLR) is smart enough not to initialise a type twice. My approach is not perfect as extensions of enumerations are not necessarily included. For a future plug-in system, some sort of InitializeOnLoad attribute could save the plug-in writers day. But my hack works 100% for all non-extended enumerations and that’s enough for milestone 1.
XAML Serialization
Step 1: Make your objects “expressable” in XAML
The “read”-aspect of XAML serialization is much more important as it is an absolute requirement for milestone 1. The tricky thing with XAML is, that you cannot express circular references for plain CLR objects (no DependencyObject). My object model, however, requires two way relationships in some places. For instance: Talents need access to the attributes of “their” hero in order to compute effective talent levels. Now since there has to be a default constructor, the hero reference will be initialised with null, resulting in an invalid state. There is no way to ensure that your object is initialised correctly, as you don’t know when WPF/XAML is “done” with its manipulations.
The only option is to propagate the hero reference down the hero graph once the hero is created, which means that even collections have references to the hero they belong to.
Step 2: Make your objects serialize correctly
Contrary to what people might tell you, XAML Serialization does not come for free. There are severe limitations and not that many customisation options. Here is how I wished I could store my heroes:
<Hero ShortName="Kyle" FullName="Kyle MacDuncan">
<Hero.Attributes>
<Attribute Level="8">Strength</Attribute>
...
</Hero.Attributes>
<Hero.Talents>
<Talent Level="5">Sword</Talent>
...
</Hero.Talents>
</Hero>
Automatically converting the hero-less Attribute and Talent to their bound equivalents, HeroAttribute and HeroTalent respectively. Interpreting this is one thing. A bit of Voodoo magic and a couple of virgins (read: TypeConverters and the like) would make this work. But as I said, there is no way to tell the XAML serializer to first convert certain values to more serializable equivalents.
So eventually I gave up and implemented XAML serialization using a pretty nasty hack: All the properties that need processing prior to assignment are loaded off into a xData class (HeroData, TalentData, and so on). This essentially means that I have to implement each non-trivial property at least twice and that extension via plug-ins has become at least twice as difficult. My serialised hero looks like this:
<Hero x:Key="codeHero"
xmlns="clr-namespace:ACFabrik.Model"
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
ShortName="Kyle" FullName="Kyle Mac Duncan"
ExperienceTotal="14" ExperienceUsed="12"
FameTotal="15" FameUsed="10" Encumberment="0" >
<HeroData LocalLibrary="{wpf:StaticResource defaultLibrary}">
<HeroData.Attributes>
<HeroAttribute Level="8" Attribute="Strength" />
<HeroAttribute Level="7" Attribute="Constitution" />
...
</HeroData.Attributes>
<HeroData.Talents>
<HeroTalent Level="7">Alchemy</HeroTalent>
<HeroTalent Level="8">Attention</HeroTalent>
...
</HeroData.Talents>
</HeroData>
</Hero>
The HeroData node defines a new attribute: “LocalLibrary”. It is used to map the talent names (and possibly others) to their definition in an external library. This way multiple heroes can share the same talents. LocalLibrary is only required when heroes are loaded as part of XAML resources, i.e. when you don’t have control over the XamlReader.Load method.
The result is a bit more verbose and not that beautiful.
Well, at least it will yield good compression ratios (the “<Hero”-prefix…).
Next Steps
On the data side, the next question to answer is how the file formats look exactly. While I now have the basic capability to serialize my objects to streams, I need to come up with concrete formats for heroes and libraries.
Also, I need to start thinking about the general UI concept. Printing in WPF works via Visuals, so I literally get previewing for free. I therefore include a very basic UI (preview + print button) in the requirements for milestone 1.
Tags: .NET, Games, Programs, Projects by SealedSun
1 Comment »