• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
Alexander Braunreuther

C++ Component Based Engine Design

10 posts in this topic

Hi,

 

thanks for your reply. We're doing this as a learning phase at the moment but the longterm goal is a bigger game.

 

As you wrote the gameplay stuff is planed to be done hacky.. We want to include some scripting language where the performance won't be that important.

I've read a lot about this caching stuff and thats not the only thing. We plan to introduce multithreading to the engine where I think this sets of systems could be very nice to use.

 

What did you mean by

 

So plan to have some broad components, where the components are really just interfacing between systems

0

Share this post


Link to post
Share on other sites

struct Entity
{
ComponentHandle componentIDs[64];

template
ComponentHandle getComponent()
{
return componentIDs[TypeID::value()];
}


This immediately stood out to me as non-ideal. This limits you to only have 64 component types, period, limits you to only have one of each type of component (not good for more generic behavior components like `ScriptComponent`), and wastes a fair bit of memory. At least move to using a run-time-sized vector of sorted typeid/handle pairs (so you can have any number of components, duplicated, and any number of component types).
 

* Is it a good idea to have many of this small systems that all got one job and prepare the data for the next system?


It can be. The devil's in the details, of course, but this approach both gives you good cache behavior (read from one stream and write updates to another) as well as great threading opportunities (same reason).
 

Sometimes i think there are systems that need a big load of lookups to work on special data. That could kill the performance, when we got a lot of data. For example: we want to calculate the worldmatrices for the rendering. So we need the positions of the meshes. Therefore we can see which entities got meshes and lookup for these the positions and calculate the worldmatrix from. That way we got the number of meshes as lookups every frame.


Think through what you're actually operating on. You can have an array of all scene nodes that just iterate over them pulling matrices from animation data or physics (or pushing rather than pulling). Keep the arrays sorted in the smae order (e.g. by EntityID) and you can do a completely linear scan of both the source and destination arrays.

That said, lots of complex modules need to work with trees of some sort. You still store the data in contiguous memory regions preferably, of course. But the modules typically are not working on components but rather their own completely separate data structures. Rendering needs some kind of tree (DBVH trees, BSP trees, octrees, whatever you're using; maybe more than one type) that just stores rendering state information, physics/raycasts need trees and physical/filter data, even AI often needs trees.

This is one of the reasons I don't usually recommend people to follow the ECS guidelines; it's a set of principles based on assumptions that (in my experience, at least) aren't always true. Not that ECS is entirely invalid, I just don't think that applying ECS principles to every single component/system is a good use of your time.
 

The Slotmap holds the innerarray with the data that is packed and two indices array that are 32 bit per index. So the arrays are everytime (4 * 2 + sizeof(componentData)) * 2 ^ handle::index (usually 16 bit). even if there is maybe just the half of the components used. Would it be a good idea to make this arrays dynamically sizeable?


The slot map pattern I'm familiar with is definitely dynamically-sized. I'm not sure if that's what you're asking, though. Edited by SeanMiddleditch
0

Share this post


Link to post
Share on other sites
This immediately stood out to me as non-ideal. This limits you to only have 64 component types, period, limits you to only have one of each type of component (not good for more generic behavior components like `ScriptComponent`), and wastes a fair bit of memory. At least move to using a run-time-sized vector of sorted typeid/handle pairs (so you can have any number of components, duplicated, and any number of component types).

 

Yeah there would a vector of pairs maybe fit a bit better.

 

So The idea of many small systems does'nt have to be that bad? .. Is it also a good idea to give only one job to one system?

 

 

Keep the arrays sorted in the smae order (e.g. by EntityID)

Don't I kneed to lookup by the ID for sorting them?

 

 

This is one of the reasons I don't usually recommend people to follow the ECS guidelines; it's a set of principles based on assumptions that (in my experience, at least) aren't always true. Not that ECS is entirely invalid, I just don't think that applying ECS principles to every single component/system is a good use of your time.

Yeah I already had something like this: The Camera.. For example there is just one in the scene at the moment so its easier to implement it as a single object.

template<typename T, typename HandleType>
class SlotMap
{
public:
    SlotMap() : m_firstFreeIndex(0), m_activeElements(0)
    {
        for (uint32 i = 0; i < Pow<2, HandleType::index_bits>::result - 1; ++i)
        {
            m_outerArray[i] = OuterHandle(i + 1);
        }

        m_outerArray[Pow<2, HandleType::index_bits>::result - 1] = OuterHandle();
    }

    ~SlotMap()
    {

    }

    HandleType add()
    {
        uint32 newIndex = m_firstFreeIndex;

        m_firstFreeIndex = m_outerArray[newIndex].m_nextFreeIndex;
        //m_outerArray[newIndex].m_nextFreeIndex = 0;
        m_outerArray[newIndex].m_validation++;
        m_outerArray[newIndex].m_innerIndex = m_activeElements;

        m_denseToSparse[m_activeElements++] = newIndex;

        return HandleType(newIndex, m_outerArray[newIndex].m_validation);
    }

    void remove(HandleType handle)
    {
        m_outerArray[handle.m_index].m_nextFreeIndex = m_firstFreeIndex;
        m_firstFreeIndex = handle.m_index;

        m_outerArray[m_denseToSparse[m_activeElements - 1]].m_innerIndex = m_outerArray[handle.m_index].m_innerIndex;

        m_denseToSparse[m_outerArray[handle.m_index].m_innerIndex] = m_denseToSparse[m_activeElements];

        m_innerArray[m_outerArray[handle.m_index].m_innerIndex] = m_innerArray[m_activeElements--];
    }

    T* get(HandleType handle)
    {
        if (has(handle))
        {
            return &m_innerArray[m_outerArray[handle.m_index].m_innerIndex];
        }
        else
        {
            return nullptr;
        }
    }

    bool has(HandleType handle)
    {
        if (m_outerArray[handle.m_index].m_validation != handle.m_validation)
        {
            return false;
        }

        return true;
    }

    /* Accessors stuff for the inner array. We don't want the user to operate on the index arrays. */
    typedef Iterator<T> iterator;
    typedef Iterator<const T> const_iterator;

    iterator begin()
    {
        return iterator(&m_innerArray[0]);
    }

    iterator end()
    {
        return iterator(&m_innerArray[m_activeElements]);
    }

    const_iterator cbegin()
    {
        return const_iterator(&m_innerArray[0]);
    }

    const_iterator cend()
    {
        return const_iterator(&m_innerArray[m_activeElements]);
    }


private:
    struct OuterHandle
    {
        OuterHandle()
        {
        }
        explicit OuterHandle(uint32 nextFreeIndex) : m_nextFreeIndex(nextFreeIndex), m_validation(0)
        {
        }

        uint32 m_nextFreeIndex : 16;
        uint32 m_validation : 16;

        uint32 m_innerIndex;
    };

    /*
     * The size of the array depends on the set handle.
     * If the index of the handle is set to 12 for example the array can hold 4096 structs.
     */
    T m_innerArray[Pow<2, HandleType::index_bits>::result];

    OuterHandle m_outerArray[Pow<2, HandleType::index_bits>::result];

    uint32 m_denseToSparse[Pow<2, HandleType::index_bits>::result];

    /*
     * The number of active elements.
     * This is used to get the index of the last active elements in the array.
     */
    uint32 m_activeElements;
    uint32 m_firstFreeIndex;
};

That's the SlotMap. I know the one you posted on your blog. But I didn't understand why its dynamically sized, because i've read often this one "You're a game programmer, you know about your data!"

So the map should be made that big as it needs to, or not?

0

Share this post


Link to post
Share on other sites

Yeah there would a vector of pairs maybe fit a bit better.


That's what I meant, yeah. See http://www.boost.org/doc/libs/1_55_0/doc/html/container/non_standard_containers.html#container.non_standard_containers.flat_xxx for what is a pretty great data structure for this kind of stuff (flat_map).
 

So The idea of many small systems does'nt have to be that bad? .. Is it also a good idea to give only one job to one system?


Evaluate it on a case-by-case basis. If you have a bunch of inter-dependent systems, you might have a problem. If you have a system that does several radically different things, you might have a problem. There is no single rule that you can just blindly follow.
 

Keep the arrays sorted in the smae order (e.g. by EntityID)

Don't I kneed to lookup by the ID for sorting them?


Maybe. Again, see flat_map. Dependending on how many items you have (and if you remember to keep PODs as PODs and trivially movable/copyable/destructible) you can get by just fine with the flat_map.
 

Yeah I already had something like this: The Camera.. For example there is just one in the scene at the moment so its easier to implement it as a single object.


I still highly recommend using components and generic game objects. Just not trying to force one architectural pattern (ECS) onto every single component type.
 

But I didn't understand why its dynamically sized, because i've read often this one "You're a game programmer, you know about your data!"
So the map should be made that big as it needs to, or not?


_Do_ you know about your data? Did you pick the right size you need after careful analysis and tuning? "64" in the component list case seemed like an arbitrary programmer number to me. smile.png

If level A needs a different size than level B, can you apply those? If so, do you force level designers to pick the right sizes? At the very, very least, avoid _compile-time_ sizes.

Do you have metrics in place to find out if you're under-utilizing the size you picked? If you have said metrics, couldn't you just as easily detect and warn if you're going over a soft limit rather than enforcing a hard one?

Are you targeting a system with constrained memory? 32-bit PC apps have 2GB (more if you set LAA mode in Windos)and 64-bit apps typically have 4+GB available for use without any paging, XBone and PS4 are both 64-bit w/ 4GB of application-addressable memory (maybe more down the road given they have 8GB). Do you really need to compromise your iteration speed and development time to make sure you don't use a few more kilobytes of memory than you expected? Edited by SeanMiddleditch
0

Share this post


Link to post
Share on other sites


So plan to have some broad components, where the components are really just interfacing between systems.

This seems like good advice to me - you should make your system flexible so that you can change it later on without rewriting everything.  No matter how much you plan ahead, no matter how much advice you get here, and no matter how much you try to optimize ahead of time, you will always find something that you want to change later.  If you make flexibility a priority from the start, you will be much better off than if you try to optimize for cache access now...

 

The other thing that I would like to stress is that you already have a sample implementation.  Why don't you use it for a while and see what you like and don't like about it?  Do some profiling and see if your cache assumptions are paying off.  Try having one task per component system, and then try it out with three or four per system.  You will learn more about the way the system works, you will learn about how to reason about your design, and you will know what to change going forward (for this project or the next one).  Doing it yourself is going to be the better learning experience, so don't be afraid to take a shot!

0

Share this post


Link to post
Share on other sites

Hey,

 

Thanks for your replies! I've thought a bit on your answers.

 

@Sean: I think a half half approach could be fine maybe working with some reserve mechanism, that uses a fixed length but haveing some more space is not bad.

The 64 is just so i got an array your right ;)

And yeah we are developing for PC only. Multiplattform would be to much work at the moment.

 

@Randy: We are trying this do it and see if it works approach a lot but there is such a big amount of stuff we have to think of that's why I asked if somebody has some tips.

But you are right as longer we work with it, there more it comes out where we have to go.

 

I came up with a new question as my friend is implementing the graphical part and makes use of textures meshes and other resources. So at the moment I have to think about resource management and can't figure out if I should do a "resource manager" or if the resources are managed by the ECS.

 

To show what i mean:

a resource manager should load, cache, give resources and so on. Lets see it for textures for example: The textures could be stored linearly withing our ECS systems or there is a manager that stores it in its structures and the ECS holds a pointer on it. That sounds somewhat against this stuff I've read. But maybe I'm again to much fixed on this linear memory problem?

0

Share this post


Link to post
Share on other sites


… So at the moment I have to think about resource management and can't figure out if I should do a "resource manager" or if the resources are managed by the ECS.
 
To show what i mean:
a resource manager should load, cache, give resources and so on. Lets see it for textures for example: The textures could be stored linearly withing our ECS systems or there is a manager that stores it in its structures and the ECS holds a pointer on it. That sounds somewhat against this stuff I've read. But maybe I'm again to much fixed on this linear memory problem?

Resource management is responsible for the lifetime of resources, while ECS (or the involved sub-systems, to be precise) is a client of resources. There can be several entities that use the same resource by their respective components. It must not be the job of a component to know that another component is using the same texture, even if the behavior is outsourced into sub-systems. So these are two totally different concerns, and hence should be handled by different systems.

 

If you have many small objects, then storing them linearly will give you an advantage if you are able to process them to some extent in order of storage. Textures are definitely not small in footprint, and you never process them in storage order. Furthermore, most textures reside in VRAM after they have been uploaded. All you need to store in CPU RAM is a reference and metadata about the texture.

0

Share this post


Link to post
Share on other sites


So at the moment I have to think about resource management and can't figure out if I should do a "resource manager" or if the resources are managed by the ECS.

 

Will you ever draw things that aren't part of your ECS? Presumably yes (for instance, in my game, terrain, water, precipitation, editor UI, etc... are not represented by entities), so the resource manager should be completely separate from the ECS.

0

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0