No3371 / DevBlog

0 stars 0 forks source link

Thingy: A Component Based Data Framework #4

Open No3371 opened 5 years ago

No3371 commented 5 years ago

Goal

To create a high performance, easy to extend/manage, component oriented data entity framework.

Possible Usage

A + B = C

The formula above are describing that:

A thing that can be referred as C is a combined result of A (referred) and B (referred) putting together.

Based on this concept, we can consider A/B as Filter, which is to classify a thing. While C is a template, which is a predefined thing with certain modules built in.

Everything is a composition

The core concept of Thingy is Something is something because it can be.

Let's say here we got a thing with these modules:

  • Object
    • Material:
    • 0.8kg Wood
    • 3.3kg Alloy
      • 3.2kg Iron
      • 0.1kg Misc
    • 0.6kg Misc
    • Dimension: 1.2m x 0.2m * 0.1m
  • Craft
    • Crafted by: Someone
    • Crafted since: 1977/12/12
    • Quality: Excellent
  • Tool
    • Usage: Hand-held
    • Purpose: Cutting/Puncturing
  • Sharpness
    • Rating: 8
  • Condition
    • Ageing: 42
    • Maintenance: 100

What could it be? I'd say it's a good old iron sword.

How could it be made? Let's make up a simple and minecraft-ish one:

Wood Stick + Iron = Iron Sword

So what is Wood Stick? If we want it to be simple, this is how you do in traditional crafting mechanic:

  • ID == "Wood Stick"

So only stuff that is labeled "Wood Stick" can fit the first slot... which is a common approach, boring and inflexible.

If we review the formula Wood Stick + Iron = Iron Sword, we could notice that, it's actually Sword Handle + Body Material = Sword. That's where we can utilize the design of Thingy, Imagine a "Sword Handle" Filter:

  • ANY, AND // AND type filter means all the sub filter has to be matched
    • Dimension
    • L > 0.2m // It has to be long enough
    • Appearance
    • Straight // It's a handle, it has to be straight
    • Condition
    • Wetness < 5 // Wet and rotten wood is not going to be a good handle

Using the filter, a lot of thing would be valid for the first input slot of this Sword recipe, even if you put in a torch, it's possible the torch body could be a valid sword handle.

No3371 commented 5 years ago

Thingy

A Material List of a thing would be:

Thingy { typeID: MaterialList }

  • Thingy { typeID: Material }
    • Int Value { typeID: MaterialType, Int: 71 }
    • Float Value: { typeID: Mass, Float: 1 }
    • Byte Value: { typeID: Purity, Byte: 90 }

Thingy is not typed

The core concept of Thingy is "Because it could be". If we rely on C# type system to define concrete class type derived from Thingy, such as Weapon, Tool, Material...

Sure, a marker identifiable by C# is helpful, but if that's the only purpose, Type object works not better then a int Type field, and it will force developer to create a bunch of concrete Thingy class that's basically same, for each Thingy concept in their mind. Even with a code generator, it'd be hard to maintain.

Thingy is locally unique

By design, a Thingy can not contains more then 1 Thingy of same type. Though it is technically possible, the complexity of the system will dramatically increase, and is not really necessary.

Instead, a specialized Thingy is created: ThingyGroup, it only accept Thingy of specified type as Modules, and could be used as a List of same type Thingy.

No3371 commented 5 years ago

Filter

There are 2 types of filter:

ThingyFilter

Filter is a pattern matching tool. Like Thingy, Filter could be nested, and a nested filter is a pattern. For example, imagine a filter that select anything that

If you apply the filter to a bunch of Thingy, anything has a structure like this will be selected:

Thingy { TypeNameID: ANY }

  • Thingy { TypeNameID: Material }
    • IntValueThingy { TypeNameID: MaterialType, 7 } (Must be 7)
  • Thingy { TypeNameID: Weapon }
    • IntValueThingy { TypeNameID: Damage, 13} ) ( Could be 11 ~ 16)
No3371 commented 5 years ago

Performance

Flyweight

A wood sword handle is very likely "could be" a torch body. Assuming 2 wood with exact same properties, the only difference is one is used in a Sword Thingy while the other one is used in Torch Thingy.

The Context is different, but nothing different inside the Thingy itself, why don't we use only 1 Thingy so we could reduce memory usage?

Thingy is immutable. We have no need to worry a existed Thingy would change, only have to keep an eye on new Thingy in order to make sure that there are never going to be 2 same Thingy.

This require hashing. If we design the system right, we could quickly check if there's any existed Thingy that is just identical to a creating one.

TypeNameID Mapping And Caching

Understandable string is required for us to interact with the data , but the communication inside the system does not.

To reduce memory usage and query time, I want to use Integer as FilterTargetID/TypeNameID, so we don't have to keep a string object in every Thingy while enjoy the fast comparison of Int.

In order to achieve this, a dynamic string<->int mapping is required.

Technically, clients call GetOrCreateTypeID(string name) of a given ThingyLibrary, which will check if the passed in name is already registered, if not, generate a new unique int ID and register the string-int pair; if registered, return the paired int.

Cross Session Consistency

If we always go through the above process for every Thingy every time, a critical problem emerge. If I create a A thingy, the library generate a int, for example, 1121 for this TypeNameID (A). Then I close the application. Will the generated TypeNameID for A still 1121?

If we use the hashcode of A as ID, maybe, that will be the case most of the time, but this is not a reliable design. A minor change in the hash algorithm of string, or a hashcode collision from different name string, the system will explode...

In order to assure consistency between sessions, we have to keep track of previous mapping, so that if a different name is registered, it wont override previous mapping. A copy of the mapping data should be saved along with the database.

No3371 commented 5 years ago

Thingy Hashing

Current Hashing


        public override int GetHashCode ()
        {
            int output = 109;
            HashCodeTraversal(ref output);
            return output;
        }

        protected virtual void HashCodeTraversal (ref int output)
        {
            output += ThingTypeID * 71;
            if (Modules != null)
                foreach (Thingy t in Modules)
                {
                    t.HashCodeTraversal(ref output);
                }
        }

Should we hash ValueThingy? Should ValueThingy be cached and shared?

No3371 commented 5 years ago

Template

Is essentially a pre-loaded Thingy that act as a prototype, for clients to reference.

No3371 commented 5 years ago

ThingyGroup

How can you discriminate between Thingy of same TypeNameID inside a same parent? I don't want to add meta data to Thingy itself, since it's unnecessary.

ThingyGroup is a specialized Thingy that only accept a specified TypeNameID and Thingy of that TypeNameID as modules.

It store extra (int, string) as index and tag for each Thingy it owns.