"Generic" achitecture

You are absolutely right I was thinking of something completely different, but in that case why not use the Component model in Monogame already.

but in that case why not use the Component model in Monogame already.

I’m reusing the IComponent / IUpdateable / IDrawable interfaces indeed.
But in monogame these are designed to be global, while in an entity component system it is meant to be local to an entity.
Also each component can use other components, so a component resolution mechanism is needed

1 Like

For those who are interested we have ECS already implemented in MonoGame.Extended.

1 Like

wow good to know! I see it’s based on Artemis, I did have a look.
I wish I knew about it earlier :slight_smile:

You could just get rid of the base classes altogether.
Each type can be the exact size it needs to be.
Alternatively…
Instead of looking at it from the point of view of wrapping the classes.

You could create a class that tunnels into each and tracks them into itself.
Now this may seem a bit weird but its yet another way to solve the problem.

Its not a generic solution, its a specific solution.
Not everything has to be generic all the time.

I doubt this is a pattern, since i just made it up.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace QuickNDirtyTunnelingIterator
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        Test test;       

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            test = new Test();

        }
        protected override void Initialize()
        {
            base.Initialize();
        }
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
        }
        protected override void UnloadContent()
        {
        }
        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();
            base.Update(gameTime);
        }
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            base.Draw(gameTime);
        }
    }
    public class Test
    {
        public Test()
        {
            A a0 = new A();
            A a1 = new A();
            B b0 = new B();
            C c0 = new C();
            A a2 = new A();

            for (int i = 0; i < IterationTunnel.thisList.Count; i++)
            {
                switch (IterationTunnel.thisList[i].whichArray)
                {
                    case IterationTunnel.IS_TYPE_A:
                        Console.WriteLine("IterationTracker.thisList[" + i + "]" + IterationTunnel.thisList[i].a_ref._Pos3D);
                        break;
                    case IterationTunnel.IS_TYPE_B:
                        Console.WriteLine("IterationTracker.thisList[" + i + "]" + IterationTunnel.thisList[i].b_ref._Color);
                        break;
                    case IterationTunnel.IS_TYPE_C:
                        Console.WriteLine("IterationTracker.thisList[" + i + "]" + IterationTunnel.thisList[i].c_ref._IsReceivingShadows);
                        break;
                }
            }
        }
    }


    public class IterationTunnel
    {
        public static List<IterationTunnel> thisList = new List<IterationTunnel>();
        public const int IS_TYPE_A = 0;
        public const int IS_TYPE_B = 1;
        public const int IS_TYPE_C = 2;
        public A a_ref;
        public B b_ref;
        public C c_ref;
        public int whichArray = -1;
        public IterationTunnel(A item)
        {
            whichArray = IS_TYPE_A;
            a_ref = item;
            thisList.Add(this);
        }
        public IterationTunnel(B item)
        {
            whichArray = IS_TYPE_B;
            b_ref = item;
            thisList.Add(this);
        }
        public IterationTunnel(C item)
        {
            whichArray = IS_TYPE_C;
            c_ref = item;
            thisList.Add(this);
        }
    }
    public class A
    {
        public IterationTunnel tracker;
        public Vector3 _Pos3D;
        public Texture2D _TexChannelA;
        //... (other mandatory properties)

        public A()
        {
            Console.WriteLine("A constructing...");
            tracker = new IterationTunnel(this);
            Console.WriteLine("A constructed");
        }
    }
    public class B
    {
        public IterationTunnel tracker;
        public Vector3 _Pos3D;
        public Texture2D _TexChannelA;
        public Vector4 _Color;
        //Other props
        public B()
        {
            Console.WriteLine("B constructing...");
            tracker = new IterationTunnel(this);
            Console.WriteLine("B constructed");
        }
    }
    public class C
    {
        public IterationTunnel tracker;
        public Vector3 _Pos3D;
        public Texture2D _TexChannelA;
        public Vector4 _Color;
        public bool _IsReceivingLight;
        public bool _IsCastingShadows;
        public bool _IsCastingLight;
        public bool _IsReceivingShadows;
        //Other props
        public C()
        {
            Console.WriteLine("C constructing...");
            tracker = new IterationTunnel(this);
            Console.WriteLine("C constructed");
        }
    }
}

The output is as follows.

A constructing…
A constructed
A constructing…
A constructed
B constructing…
B constructed
C constructing…
C constructed
A constructing…
A constructed
IterationTracker.thisList[0]{X:0 Y:0 Z:0}
IterationTracker.thisList[1]{X:0 Y:0 Z:0}
IterationTracker.thisList[2]{X:0 Y:0 Z:0 W:0}
IterationTracker.thisList[3]False
IterationTracker.thisList[4]{X:0 Y:0 Z:0}

The trick here this time, and you can make up different takes on it is…
We just replace the type with a int, so we don’t have to cast we just catch it with a proper type or just call on the instance. Thus avoiding boxing unboxing.
We replace the container with a injected tracker into each class to be tracked.
We get rid of the inheiritance so we don’t have to worry about vtable calls.
We self register on object construction to the tunneled tracker by reference.
We can keep the instance or we can iterate the tunnelers list that links to it by reference.

Note you don’t have to have a tracker in each class you could just call new IterationTunnel(this); all by itself i left it in to show how you can link from the class to the list bi directionally if desired.

Btw on the earlier suggestion of Arraylists. Be Warned ! they are not only slow, worse they treat everything as if they are objects internally. Meaning they box and unbox everything.

The big reason to avoid properties in my opinion is you could potentially be hiding side effects that the setter/getter is hiding. Any setter/getter could have any amount of code in it. To hide that via a call to something like ‘someObject.expensiveSetter = thing’ isn’t fantastic. We also get in this nasty situation where our instance variable declarations have some ugly as hell syntax with all that get/set junk and curly braces all over the place. We get code mixed in with those declarations making it harder for people new to the code to parse it. Lots of good reasons to avoid them and very few to use them.

…it breaks the code guidelines we all come to agree upon…

I don’t remember getting a call from Microsoft to voice my opinion :wink: I didn’t agree upon any of what they came up with for the code they write. If I learn of a better way to do things I will not hesitate to adopt it.

Microsoft made a decision on conventions 10 years ago when the language was vastly different than today’s C#. They are bound to it. Your code is not. Come up with your own conventions and just implement them consistently across all your code and you’ll make your life a happier place :slight_smile:

Basically my point; it’s hard to have “generic” architecture; it’s going to depend on so many factors. But there are some patterns and solutions that do pop-up time after time in various languages and that’s why I suggest @Alkher that you should read up on patterns found in game programming.

If you write your own properties, you know what the getter/setter do.
Writing string MyProperty {get;set;} isn’t more expensive than string MyField; as the compiler treats a property with empty getter/setter just like a regular field.

Today they are focusing on .NET Core and they are free to change what they did wrong. Looks like they haven’t seen anything wrong with properties.