crancran

Members
  • Content count

    159
  • Joined

  • Last visited

Community Reputation

505 Good

About crancran

  • Rank
    Member

Personal Information

  • Interests
    Design
    DevOps
    Programming
  1. C++ General C++ class questions

    Personally, I would tackle your problem a bit differently. I would either use a factory class or at the very least a static factory method that does the load / exception logic outside of the model's constructor. Only once the file could be located and loaded does the factory class or method allocate a model and populate it. No, the problem is how you're declaring the GameWindow variable. I would have expected something more along the lines of the following instead: class Game { public: Game(HINSTANCE hInstance) : gameWindow( new GameWindow( hInstance ) ) {} ~Game() { delete gameWindow; } private: GameWindow *gameWindow; } Or better yet, use a smart_ptr.
  2. I looked over your code and I have a few suggestions / comments. What purpose does making Manager a singleton?  That choice effectively implies your World is a singleton and to me that imposes an unnecessary design restriction that serves no purpose.  It's not uncommon to manage different simultaneous simulations, particularly in game editors.   As an editor example, you have a surface where you render your game world simulation where you're editing the level, one where you render the game world in play-mode, and others where you render things like a material editor or some compositor node hierarchy.  You might actually have different levels loaded all being rendered separately, all which require their own World instance.  In all these situations, you may also have different systems at play and thus each World simulation should be tied to a specific finite list of applicable systems.  A generalized class like Manager to me seems like an unnecessary abstraction, one which a World can fill easily. Also somewhat related, you have several methods on Manager which seem ill-placed like create_entity, add_velocity, and add_position2D.  I would probably suggest something like a ComponentFactory and possibly specializations for each component-type that you interact with to create your components.  The allocation/destruction of entities would be a function of your World or some associated Scene object that your world has a reference to.   In the end, this notion of Manager goes away and it leaves you with a World where you might actually register the systems with instead.  If you truly want to avoid having to register systems with each constructed World, you could use the factory pattern again: class WorldFactory { typedef std::vector<System*> SystemVector; SystemVector systems_; public: //! Register a system with the WorldFactory void registerSystem(System* system); //! Creates a world, adding each registered system to the World's simulation list. World* createWorld(); } I still disagree to a degree because I believe each World should be constructed with its own necessary ticked systems and nothing which is unnecessary, but if you at least want something generalized, this approach at least provides an flexible and useful design pattern instead of the Manager singleton you had.  You decide based on your need whether you create and reuse the same WorldFactory or if you create multiple instances as needed. Why not? Lets take a system that renders a sprite at a specific position.  Such a system checks the entity when the "OnEntityAdded()" callback is fired and looks to see if that entity has a sprite and transform component.  If it does, it adds it to that internal list and if not, it isn't added.  It's assumed at this point that entity has both components.  Then when the "OnEntityRemoved" callback is fired, the system simply removes that entity from any internal lists it maintains because the entity is being removed from the simulation. Just remember that a system doesn't necessarily imply a 1-to-1 relationship to a component.  There are definitely use cases where different component types (where applicable) are processed by the same system.  For example, my RenderSystem knows how to draw 2D sprites and 3D meshes.  While the components for these are different, the same system operates on these.  So internally, that system maintains multiple lists and just handles updating each list accordingly during that system's update tick.   I personally advocate that your components represent the user interface into the underlying framework.  The systems may expose various means for other systems to interact with one another but generally I would say that from a game designer perspective, the component's attributes are their path into influencing behavior.
  3. ECS design

    There is also the idea of a layer of indirection and the use of the observer pattern.   Consider for a moment when an entity or component is either added or removed.  In these cases, systems would be notified of the state change, allowing them to deduce whether the entity should remain registered in the system or removed.  It's at this point where the system would also create a single, unified representation of state that it iterates during the frame update.   One benefit here is that your system update loop no longer must be concerned with determining the right entity list to update.  When entities or components are added or removed, that internal unified representation is manipulated, which is the basis for your game update loop for that system.   Now the system's update becomes focused solely about data.  Perhaps some event sourcing system is used to track state change and replicate it to other systems.  Perhaps the system uses incremental small update loops to replicate state from one component to the unified representation.  Perhaps your unified representation holds a weak-reference of sorts to the real components, which allows for quick, easy lookup of the component.  But the goal here is that the system should be as solely based on the unified representation as possible, not the component.   In short, I prefer to view components as simple data bags who's sole responsibility is to seed user state to the simulation and allow easy ways to query and get state from the entity system.  The introduction of the extra layer of abstraction with a unified system keeps system-private state out of the component, allowing the component cache array to remain as small in size and on par with its intent.  Should one system need something specific not exposed by a component from another, perhaps an event should be raised to send that value across systems or merely have system 2 consult system 1 for said data.   The extra layer of abstraction also allows the component and system unified object to change independently of one another.  As new contextual values are determined for the system to operate, the internal representation changes without impact to the component.  The only real consequence here is you need a few loops to copy data periodically, but such loops are again often cache friendly and benefit from parallelism.
  4. Avoiding the God Class

    I would say you're already doing quite well by separating distinct units of code into modules.     But be careful not to over think the single responsibility principle or separation of concerns.  Introducing an Engine or Application class that acts as glue between those module units isn't necessarily something which needs to be pruned immediately.  It's often a good way to let new ideas and solutions percolate and as you see enough code of a specific use case building up, refactor that into its own module or class as needed.   At the end of the day, all engines have their fair share of glue code. 
  5. entity system questions

      If your sector has a collider component, when new entities collide with it (aka enters the sector), the collision system informs other systems of said event.  Your CutSceneSystem may be interested in collisions between Sectors and Entities and if such a collision event is raised, it gets the current sector id/name, locates the cutscene files and begins their playback for the user experience.  In this case, its a combination of the Physics System and a Game System interacting by way of events/messages/callbacks.   As for gates taking players to new sectors, this again is basically a collider component in the ECS that raises a collision event that causes your SectorTeleportSystem to fire.  It begins by playing a wormhole warp cutscene sequence while the new sector is loaded from disk.  After loading is complete the cutscene sequence ends and you find yourself in the new sector.   As phil_t said, some systems can respond to game world events/triggers but are not necessarily manipulating entities.  The events/triggers they do react to however can very much be something that the entity component system triggered based on some component system that manages a particular game aspect.
  6.   In complex situations where a system requires multiple components to perform it's job, I tend to side on the caching approach.  Basically the system uses the notification callback when entities are added, changed, or destroyed to manage an internal list, a list I prefer to call node list.  How and when you elect to transfer component data to the node list is entirely dependent on how the system must update in accordance with your game loop.  You may need to use a command pattern to delay the update until the system's update loop or it may be safe to immediately update the node list directly.   A render system for example may require a Transform and Renderable.  As entities are added, changed, or destroyed the render system maintains a list of RenderNode instances that basically cache the transform and renderable data from the components.  The render system's update loop uses the RenderNode list rather than the components themselves as the basis for it's update loop iteration to maximize on cache friendliness.   Nothing says a system must maintain a single node list either.  In fact, it really should be a system detail that dictates how many node lists it must maintain in order to efficiently perform it's update phase.  Multiple lists are often used to avoid if/else branch statements inside the loop which can be costly on cache friendliness.
  7.   An entity manager generally doesn't exist more than once, at least not within a given simulation.  So unless we're splitting hairs over terminology here, I certainly would not advocate for such a complex setup with multiple EntityManagers because, if those entities need to interact with entities of another EntityManager; you'll have a bit of work to make that happen.   I would split your problem into two layers.   First, create an empire system that acts as a broker for all entities that exist in a particular empire.  When an empire gets created, this system knows about it.  When an entity is spawned that has an empire component that references a specific empire, this system associates that entity to the given empire.  Any communication among entities within an empire could be funneled through this system since it's goal is to act as a broker.   Next, I would have another system that sits on top of the empire system that acts as a mapper between a Player and an empire or league of empires.  This way if you decide to allow a player to control multiple empires, this system can coordinate that and handles the relationship to a specific player instance.    If you keep your system contracts clean, you should be able to change how an empire works without it impacting it's relationship to the player and vice versa.     A little bit of abstraction can go a long way to keeping code clean, easy to maintain, but also allows to future growth in game concepts.  It's just important not to take it too far to the extreme.
  8. How you interact with the information in-memory at run-time does not have to dictate how you need to serialize it.  For example, if you were using a database to store quest and achievement data, you would typically store that in a normalized manor across several tables.  During the start-up phase of your game, you would read your database and construct in-memory objects of this data that are designed for run-time efficiency. 
  9.   The reason you find so many variants of an entity framework is because there is no standard practice.   As for the queue, you could implement a single or multiple queues depending on your needs.  Chances are a single queue would be sufficient as a starting point and build upon that idea long-term if you find you need to separate the queues for any particular reason.
  10. Correct.  Think of this as a message that contains the id of the entity, the position, and orientation.  The render system takes the entity id, locates the Irrlicht scene entity by a reverse lookup table and then modifies the render scene node appropriately.     Correct.  The wrapper system transfers all information from the entity's components into the Irrlicht scene such as position, orientation, setting appropriate mesh, material, etc.  After all that has occured, this same system could just then call Irrlicht's update to send the render queue to the graphics card and swap buffers.
  11. There are two approaches you can consider for replicating information to your render system and a lot of it depends on how coupled you want your code.   The first approach would be to decouple your entity system and rendering system entirely.  There is lots of benefits to this approach as it will allow you to easily replace your rendering engine with another but does come with it's own set of concerns.  To decouple the two entirely, you would use a command queue where your ECS systems emit commands as they perform various operations.  These commands are placed into the said queue and during the render system's update phase, it would parse these commands and perform the necessary rendering operations.  It's important that all pertinent information be included in the command to avoid the rendering system having a need to query the ECS at all.     The second approach would be to live with the fact there will be some coupling.  In this case, you would have a specialized ECS system or game loop step that runs after you have completed your logical updates and would query various entities and replicate necessary information from the ECS into the render system's scene manager.  This system acts as a wrapper around calling Irrichlt's "render/update" call as it would perform the various scene updates and then render a single frame.   Both approaches have advantages and disadvantages and you can easily change between one or the other.  Pick one that makes the most sense for now and move on.  You can always come back later and change it and improve upon it as the need arises.
  12.   To avoid this, edge cases should be worked in not as hacks to the existing code base but as full fledged iterative parts of the overall solution.  But unfortunately, developers tend to be lazy and take the least path of work due to various reasons and this is one of the largest contributors to the problem you described above.  Whether you're doing something insanely simple or something far more complex and involved, the same series of iterative steps should be considered, validated, and executed.  
  13. If you ever create a simple basic windows based application, you'll notice that shortly after the window has been created, you enter into an infinite loop that looks something like the following: MSG msg; while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } This message loop calls GetMessage which blocks until a message arrives on the event queue.  When an event arrives, the loop translates it's information into locale-specific values and then dispatches it to the appropriate receiving window's procedure (WNDPROC) handler.   The above code works well if you want your main UI thread to be dormant while it waits for incoming events from the operating system, but is far from ideal if you want to continue doing things in the loop.  This is where PeekMessage becomes useful. MSG msg; while(!bExit) { while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); if(msg.message == WM_QUIT) { bExit = true; } } /* do your stuff here */ } Whether you're doing console-based or window-based Win32 applications, the above loop will allow you to handle operating system events, allowing the window to remain responsive while also allowing you to perform whatever tasks you need to do for your main loop.     The biggest downside to the above is that if your stuff takes a bit of time per main loop iteration to complete, this can introduce input lag to your window message loop and could still make the window's title reflect "(Not Responding)".  You'll need to experiment to see whether the above approach with PeekMessage will be sufficient for your needs or not.  Chances are it should be more than sufficient at first but as your processing for stuff takes more and more time, you might find the need to split the two loops into two separate threads.  The general idea here is that you'd use the first loop I presented above in conjunction with a bit of logic to capture input from the message loop, convert it into some data structure and dispatch it to a command queue that your second stuff thread inspects during each of it's loop iterations and processes input.     My suggestion is not to introduce any parallel threading to the mix until absolutely necessary as it does add a layer of complexity which can be hard to manage if you're not familiar with multithreading and shared data access requirements across multiple threads.
  14. Did I begin learning late?

    Late?  Not even close.  In fact, I'd encourage you not to forget to be a kid either.     Lots of times, avid programmers who start at a young age find themselves buried in learning all this information that is at our finger tips and we forget to be kids and have fun and do the stuff kids do.  One day, that "me" time won't be as prevalent as responsibilities take hold and you start a family and begin in the workforce.   So cherish your time wisely.  If you want to learn how to program, networking or whatever.  Do it for you and not anyone else.  Do it because you want to and not because you feel compelled to prove your self worth to someone else.  But as with anything, moderation and don't forget to have fun along that journey! 
  15. Component-Entity game design issue

    What JTippetts described for the animation scenario is just one of several ways to approach the problem.  Another is to approach it in a more event-oriented fashion where the AnimationController subscribes to various events that indicate combat, movement, idle, etc.  When those events are fired, the AnimationController merely updates it's internal state machine.  The state transitions trigger a series of additional events that the AnimatedModel has subscribed to and it plays out those animations.   But in this case, what you've done is abstracted away what really is an explicit dependency between two components.  Sure, one could argue that you could replace the AnimatedModel component with some other behavior that listens to the same series of events and you could then swap between the implementations on a per entity basis.  This has some merit, but such a design should come from actual need rather than over engineering a solution that you may never utilize and you find is much harder to maintain than had you just kept the dependency explicit.   There is no formal approach here and there a literal dozens of ways you can approach this.  Some people advocate duplicating data in cases of transform to avoid cache misses during various update passes while others design some elaborate handle-to-id lookup indirection table so that the cost to get a component of a type on a per-entity basis inside a tight loop of components is minimized.  But in both of these designs, this should be something that comes from profiling to determine whether its really a bottleneck.     You could also have system A that processes component A lookup whether the entity has component B when the system A is told that a component has been added/removed from the entity.  The system maintains a list of entities w/component A & B and a list that has component A but no B.  This way you can do specialized cache friendly operations on the entities with A&B and perhaps other operations cache friendly on those with just A.  This avoids the lookup and branch problems that come up with if-conditions inside loops.   In the end, all this boils down to tuning and profiling more so than the design and implementation of a component entity system.