Skip navigation.

Software Development

Correcting The Future

Project V: Listing Types

I've updated my TODO list for those curious as to the status of Project V. I can now edit values for types from within the IDE. Take a look at this partial screenshot.



Just a little recap as to how types are defined in Project V. There are three kinds of entities.

1. Types
2. Modifiers
3. Instances

A type is the most powerful and flexible entity. It contains sets. In the above screenshot, you can see a type by the name of Integer. It has one set called Properties. The Properties set is special in that it defines the meaning of the type itself. You can see three of those properties: Size, Signed and Endian. As you can see, we have defined a 4 byte signed integer that is stored in little endian order. Simply use or create a type with different properties to get a different kind of integer.

The last line is MyInteger. This is a derived entity from the Integer type. Since it has a value (22), we can infer that it is an instance. So by creating an instance entity and deriving it from a specific type, we create an instance of that type. From the properties, the IDE knows that 22 takes up 4 bytes, is signed and is stored in little endian order.

To really screw with your head, consider that each of the properties are also instances and are defined exactly like MyInteger is defined. Note that the size property has a base type of Integer of which it is helping to define. In Project V, circular type definitions like this are no problem.

Here is C++ style pseudo code to explain the Size property.

class Integer
{
Properties:  // Imagine this defines a set
  Integer Size=4;
  Bool Signed=TRUE;
  Endian Endian=little;
} MyInteger = 22;


(Heck, I may allow users to create type definitions in this manner. Look at the Size property again. If Size is itself an instance, what is the size of it and what property defines it? Answer: itself!)

So far I've explained types and instances. But what about modifiers? Modifiers and instances are almost identical. While types can have many other base types, modifiers and instances can only have one direct base type. And the only difference between modifiers and instances is that a modifier will not create an instance. So you can use a modifier to override properties in base types. In fact, MyInteger is derived from the Int32 modifier which contains the properties you see in the screenshot above. And the typename of MyInteger would be Int32, NOT Integer as that is further up the hierarchy.

Here is really what's going on.

type Integer
{
Properties:  // Imagine this defines a set
  Integer Size;
  Bool Signed;
  Endian Endian;
}

modifier Int32 : Integer
{
// Override the Properties set in the Integer type
Properties(Integer):
  Size = 4;
  Signed = TRUE;
  Endian = little;
}

type Enum
{
// The Options set will contain the items of the enumeration.
Options: 
}

type Bool : Int32, Enum
{
// Override the Options set in Enum.
Options(Enum): 
  Bool FALSE = 0;
  Bool TRUE = 1;
}

type Endian : Int32, Enum
{
// Override the Options set in Enum.
Options(Enum):
  Endian little = 0;
  Endian big = 1;
}

Int32 MyInteger = 22;


So how should I show the typename of all the properties? Most property editors don't show it at all. I don't really want to widen the treeview to display this information. A rollover popup might be good. However, I'm not sure what's the best way to go. Any suggestions? Maybe I could toggle it on or off and display instances as "Int32:MyInteger". I'm still not sure.

I'll be working on connecting components together next. I'll have to display instances and its type hierarchy as well. Moreso than what's in the screenshot above. That's mostly for updating existing data structures and values.

So what should be the very first component to show itself in Project V? Addition? Or maybe a more esoteric component? And if there are suggestions for built-in components, let me know. Right now, I have this potential list (which will only work on integers to start off until I can make generic versions).

Multiplexor
Demultiplexor
Compare !=
Compare ==
Compare <=
Compare <
Compare >=
Compare >
And
Or
Xor
Not
Div (two outputs. Quotient and Remainder)
Mul
Subtraction
Addition

The multiplexor and demultiplexor are required as this is how you select where data goes. But if I didn't care about speed, I could build them out of the other components.

The Demultiplexor takes two inputs (and has two outputs although only one output is activated at a time). The first input is a boolean selector that selects what output to send the input. So there are two outputs named TRUE and FALSE which are activated depending on the state of the selector.

The Multiplexor has three inputs and one output. It again has a boolean selector and chooses either the input with the label of TRUE or FALSE depending on its state. The selected input is sent out on the output and the other value is discarded.

I'm thinking of building a swap component based on a selector instead of the multiplexor. Three inputs and two outputs. Basically two inputs are sent directly to two corresponding outputs if the selector is FALSE. If the selector is TRUE, then the inputs are swapped. That may be more useful than a multiplexor. If you don't use the second output, you can simply leave it unconnected and you have exactly the same functionality as a multiplexor.

And believe it or not, you can build anything and everything possible with that setup though I wouldn't recommend using such low level components for actual software development.

That would make a total of 16 components, many of which can be built from a subset of that same list. Actually, you only need two components to build anything you want. A NOT component and your choice of AND or OR component. You decide which one you want. In fact, you could combine them and only have ONE component (NAND or NOR). So it's not the computations that are critical. It's the ability to link those computations together. That's where the power comes from.

Project V Primitive TypesGPU: Antialiased Circle Drawing (w/ screenshot)

Comments

Anonymous 26. October 2008, 00:38

Josh writes:

You may already have read this, but you may be interested in Steve Yegge's lastest post about the "Prototype Pattern" which seems to be similar to how you have done your type system within Project V where, if I am not mistaken, you support the definition of new types by just changing the properties of an existing type/value and giving it a new name.

Vorlath 26. October 2008, 01:39

I think you've accurately pointed to a description of the internals of my type system.

Just on inheritance, the code I showed above is exactly what is quoted here from your link.

With prototype inheritance, each property list can have a parent list. When you look for a property on an object, first you check the object's "local" property list, and then you look in its parent list, on up the chain.

As I described in the Overview, the simplest approach for implementing inheritance is to set aside a name for the property pointing to the parent property list: "prototype", "parent", "class" and "archetype" are all common choices.

It's unusual (but possible) to have a multiple-inheritance strategy in the Properties pattern. In this case the parent link is a list rather than a single value, and it's up to you to decide the rules for traversing the parent chains during lookups.

The algorithm for inherited property lookup is simple: look in my list, and if the property isn't there, look in my parent's list. If I have no parent, return null. This can be accomplished recursively with less code, and but it's usually wiser to do it iteratively, unless your language supports tail-recursion elimination. Property lookups can be the most expensive bottleneck in a Properties Pattern system, so thinking about their performance is (for once) almost never premature.



I do it a bit different. I use sets instead of having global property names. And those sets are attached to a particular object. The reason for this is multiple inheritance. I want to be able to specify which parent object's properties I am overriding. So you can separately override two different sets that have the same name in two different parent objects.

The inherited property lookup is somewhat like what is described in the quote above except I go one further. I can flatten hierarchy trees so that all properties are directly accessible. After flattening a hierarchy, I can simply lookup the property directly. No need to search upwards. The drawback is that flattening a hierarchy needs to be done again every time a property changes. I'm going to be adding a way to identify hierarchies by SHA ID. That's next up on the list. This will provide fast type lookups instead of comparing each and every property to find out if two types are identical.


The solution for Morris is to have a special "NOT_PRESENT" property value that deleteProperty sets when you delete a property that would otherwise be inherited.



and from my actual code inside my "object" definition,


#define NENTRY_FLAGS_VALUE 1
#define NENTRY_FLAGS_LOCK 2
#define NENTRY_FLAGS_DELETE 4
unsigned int Flags;


And then there's this:

In particular, if you read an inherited property, it gets the value from an ancestor in the prototype chain. But if you write an inherited property, it sets the value in the object's local list, not in the ancestor.



My screenshot above is showing exactly this! You can't see the editor in action (like when I change 22 to something else), but when you apply a value, it sets it on the local object.

Many implementations of the pattern offer "frozen" property lists. Sometimes (e.g. for debugging) it's useful to flag an entire property list as read-only.



See NENTRY_FLAGS_LOCK above.

Performance is one of the biggest trade-offs of using the Properties Pattern.



Strange, I created this type system because I wanted performance.


If you know all the properties in a given plist at compile-time (or at runtime early on in the life of the process), then you might consider using a "perfect hash function generator" to create an ideal hash function just for that list.



I have a class called NID just for that purpose. I actually use it for two purposes. The first it to identify 'type' entities. And the other is to reduce frozen hierarchies to an ID for faster type comparisons.

I also have the advantage that some properties are design time only and affect type comparisons and other properties that are runtime members and do not affect type comparisons. So I don't have the same drawbacks mentioned in the article. I can change members at runtime without recalculating the ID.

The most common workaround to the stale-cache problem is to keep a separate data structure mapping plundered objects to their plunderers. It should use weak references so as not to impede garbage collection. (If you're writing this in C++, then may God have mercy on your soul.)



I use what is called an XTYPE as a flattened cache.

I guess I should start praying?

The solution I came up with was transient properties. Each object has (logically speaking) two property lists: one for persistent properties and one for transients. The only difference is that transient properties aren't written out when serializing/saving the player (or monster, or what-have-you.)



Yeah, I ran into this problem very quickly. That's why I started with two sets (or property lists). Properties and Members. Properties are designtime and Members are runtime. But as we all know in programming, if you can have two, you can have as many as you want. So you can now have as many sets as you'd like and you can specify if they are runtime or design time. They also allow other attributes like storing objects directly instead of name/value pairs. That's how the type hierarchy is done. Each object has a Derived and Base set.

For performance – both for network overhead and disk storage – you might want to consider designing a compressed binary format.



Already done.

If you need a GUI for object edits, you'll probably need to do some custom UI work.



Yeah, I kinda figured that one out the hard way.

Once people start adding properties, especially if they're using programmatic construction of key names, you'll have trouble on your hands if you want to try to refactor the whole system...



Already have a solution for this. I have remapping constructs that enable the replacement of existing types and components. It's necessary because of the data flow system. I suppose Steve doesn't need data flow, so his hand hasn't been forced.

Well, all I can say is that if anyone is interested in having a system that uses the Prototype pattern, Project V will be one such option. This is true even if you don't care about the concurrent and distributed functionality available in it through the use of dataflow.

How to use Quote function:

  1. Select some text
  2. Click on the Quote link

Write a comment

Comment
(BBcode and HTML is turned off for anonymous user comments.)

If you can't read the words, press the small reload icon.


Smilies

December 2009
S M T W T F S
November 2009January 2010
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31