Jump to content
  • Advertisement


  • Content count

  • Joined

  • Last visited

Community Reputation

5959 Excellent

About All8Up

  • Rank

Personal Information

  • Role
  • Interests

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

  1. From the way the embedded code is shown, I would say the first thing to do is reverse the logic. What I mean is that what you posted suggests that the code is along the lines of "I'm trying to interact with you" and "I need to figure out what you are and what I can do to you". Conceptually of course is the most sensible solution, unfortunately in practice it is also the solution which fails the most often. Rather than this, I've always used subject oriented programming for interactions. This reverses the logic such that rather than the 'doer' figuring things out the subject of the interaction performs the work. Basically this means that the input system would simply set a flag on the input component saying "this entity wishes to interact". The interaction system can then do pre-cull for subject entities which are in range, which ones can be used from the doer's position, etc. Finally, you end up with a door that runs the rule checks: "You are in range, you have my key, it is Monday after noon, you are wearing a purple shirt... Ok, guess I will open now." As to how you implement these things in the ECS itself, I suspect everyone does it differently. Personally I use a handle based approach where the interaction component simply contains a handle which refers to usually a script which checks the interaction rules. The script gets the doer and subject ID's so it can check states and make the decision. Then, likely in another script, an action is performed. Overall this probably sounds ass backwards but it is a well proven solution to removing massive 'if/else' checks. It is also great for expansion packs and such since all the new logic is contained in the subject and you don't have to patch the doer code to understand the new interaction. Just as an example, The Sims used(still uses I assume) this subject oriented approach which allows DLC to be dropped in and mostly just work. Hope this makes sense.
  2. This debate will pretty much never die. Even with the current environment of high speed internet and low pings, there is still enough of a difference which folks can argue around forever. Personally I see it as a matter of degree's anymore. "Technically" UDP is the better technology for games and it avoids a number of the downsides of TCP but the differences at the end of the day are fairly small at this point and getting smaller. The primary issue which folks bring up most often is that because TCP is lossless it will sometimes block IO when a packet is dropped which can increase the effective latency considerably. Such issues can cause hickups in the gameplay and if they are bad enough make a game unplayable. UDP can avoid these problems because most implementations send a steady stream of packets with updated information and missing one packet doesn't hold up later packets which contain more recent data such that the missed packet can safely be ignored. There are a number of reasons that this is becoming less of an issue, at least for games. The primary reason that this is becoming 'less' of an issue, though by no means gone, is that the internet in general has changed quite a bit over time. Packet loss is actually not particularly common anymore and actually less likely with TCP due to optimizations to the routers which try there best to prevent dropped packets on TCP specifically. Historically I used to measure between 1 and 5 % packet loss from most ISP's depending on time of day, anymore it is at worst around 2% unless they are having actual issues. Also, many routers have a reliability system where rather than waiting on the client or server to resend a packet the router which dropped the packet will automatically re-request it from the closest link. Often such corrections are sub millisecond and barely notable. Finally, even if all the routers are ancient and there is high loss rate, the normal pings between client and server are considerably lower such that the corrections happen in 10-20 ms versus what we used to deal with around 100+ ms. Now, as Kylotan says, super fast games can always benefit from going UDP. I might argue just how much but that is still a fairly reasonable fact. But, for the reasons I point out above (and lots of other reasons), it is becoming less of an issue and may not matter for most games moving forward.
  3. Looking at the code in that presentation it seems that the minSeparation is a measurement of how close two entities will be at their closest point. Using this value it then says that if the separation is more than some value (2*radius in the code) don't bother building an avoidance force since the entities never come close enough to need adjustment. That would be my guess from briefly looking at the code. Hope this helps.
  4. There are two usual suspects for the differences you are noticing. The negation usually comes from setting up the variables involved. A very common item is that the equations will show, for example, "target-source" and some parts of the surrounding code will want the opposite "source-target", the programmer just uses the most common case and corrects the given equations by dropping or adding the negation. This is not usually a bug, it is just refactoring the equation to fit with the surrounding available data. The second problem, and why the math does not always match up with the proper equations, is that the equations return an instantaneous force which needs to be applied. When you start factoring in other things such as a given bodies mass so they don't look mechanical, standard delta time application of forces rather than instantaneous, you are effectively breaking the equations. It is actually quite difficult to factor things like mass all the way back to the initial equations of motion, as such many implementations tweak the post motion equation to simply 'look good' rather than be correct. Unfortunately these forms of tweaks are use case specific usually and black magic tweaks that are not 'correct' in the math terms but get the job done for the game in question. Additionally, such hackery works great with some of the equations or are not actually needed in others, but variations of 'arrive', such as intercept, are extremely sensitive to such things. Tweaking the 'arrive' behavior is problematic since it almost always ends up underestimating the amount of deceleration required to stop at the target, the math gets pretty hairy to integrate this correctly so lots of folks just hack around till it is close enough.
  5. I can think of one reason, which OP doesn't cite - the service locator pattern is just a "better" way to have singletons. Being able to switch out objects from the service locator at runtime is nifty, but it still has the problem that globals and singletons have where it hides dependencies within the methods that use it, rather than making them explicit,. The service locator still looks like global state to client code and most implementations of it still only allow one of each type of object to be located from it - both of which are problems. I see it as a crutch for dealing with globals-heavy legacy code; in new code, it'd call it an anti-pattern. Well... I don't promote the idea of using the locator to hide such details, I only mention it as "you could" do so, not should. And I agree that such things should be avoided as a better practice. I tend to get into that a bit more when I mention minimizing the referential chains, those are just evil. I'm unsure of the OP's desires though, so when you have a hammer, everything looks like a nail kinda applies.
  6. Ah, you are breaking SRP at multiple levels then. But, even without that, the suggested referential chain is another case of writing a helper function rather than typing that over and over. So, you could rewrite that chain as: BindPointSampler<PipelinePS>* GetBindPointSampler(engine, slot) { return ... chain of references ... ; } This goes back to the cohesion problem. By writing the chain of dereferences you are exposing the ownership into your code. If you encapsulate the chain of dereferences as above, if and when you decide to change the referential chain you don't have to go hunt down every use case, just change this helper function. Additionally, you can write a couple variations such that if you already have the state manager, it will automatically start at that point rather than going all the way up to engine. Obviously though you should be keeping your reference chains as short as possible. Anytime I see a reference chain longer than two hops, I tend to think there is something wrong. GetSwapChain(engine) is probably a bad chain, GetSwapChain(window) or GetSwapChain(renderer) are both shorter and imply more context around what you are doing and as such, have less of a cohesion problem exposed. Or, even better, hide the swap chain completely and only have GetCurrentImageView(renderer). Utilizing helpers in this way means you don't expose the ownership outwards from the things that actually need to know such things. When done well your high level code is extremely simple and linear and independent of how you might want to restructure things later.
  7. Actually the question here is why are you not using the service locator you mention in the OP? I.e. you should rewrite that function as: CreateWhiteTexture(ServiceLocator*, GUID, string); There are quite a few benefits to this approach. Primary among the benefits is that you can change everything about how to create a texture, update this function to match and not have to touch any of the uses of the function afterwards. This is the pattern I tend to use all the way from the main function on up. If you centralize everything around a combined Factory/Locator abstraction there is a little bit of extra setup boilerplate 'within' wrapper functions such as CreateWhiteTexture but very little when using the function. I.e. in my codebase it would be something like the following: TextureBase* CreateWhiteTexture(ServiceLocator* locator, GUID guid, string name) { auto driver = locator->Get<RenderDriver>(); auto resMan = locator->Get<ResourceManager>(); ... make a texture etc ... } That's not *too* much boilerplate to deal with once in a while when writing these helpers is it? I admit, I tend to take this to a bit of an extreme level given the choice simply because I've been bitten by singletons so many times over the years that I never want to see such things in the future if I can help it. Going back to hodg's reply, I don't even believe in singletons for executable resources for the most part. For instance, the command line passed into main, my "Application::Main" never see's that, it can get the 'environment' object from the locator passed to it after the per platform main/WinMain/DllMain/whatever has setup the process. It's a bit extreme but can be really useful for instance if you ever worked on an MMO and needed to startup 100 headless clients to stress test servers. Starting 100 processes is typically bad, but if you have abstracted away the entire environment from main on up, you can just call "Application::Main" 100 times even passing in custom command lines, different cin/cout abstractions (i.e. feed to logging instead of console) etc etc.
  8. All8Up

    City building game inner workings

    There are quite a few methods to approach this but the most simplistic one is usually based on the idea of breaking everything in the world into actions which produce state changes. Banished is probably the most simplistic of the games until you get into the market and home management portions of things so I'll use that as an example. If you ignore everything except one building, things become simple though you have to think of the solution in a kind of odd manner. Let's take the fishery as an example. The fishery is placed near water and it contains one action from then on: "create a fish". Since in Banished you assign workers to buildings (well you set the number and the game selects the specific entities) the first step is pretty easy, on every game update the workers check their assigned building for an action if they don't have one already. Since the fishery always contains "create a fish" as an action the worker will always have one copy of that action in their queue. We'll ignore the other items such as get warm, go eat, sleep etc but they are also in the queue and that's where the priority portion comes in later. Every action has a required state, what it 'does' code and a final state change to make on completion. The required state for create a fish is 'in the building', it's 'does' code is simply wait 'x' seconds and the resulting state change is '+1 fish to inventory'. So, the entity tries to run the 'does' code but finds that the requirement 'in the building' is not satisfied. The entity looks in a table of actions for one which has a result of 'in a building', it should find a 'path to building' action with the desired result. So, the entity code moves the "create a fish" action to be a child of a new copy of "path to building" and starts running that action. "Path to building" has a required state: "has path to building", it searches the actions and finds "create path", pushes "path to building" to a child of a new copy of that and starts running it. "Create path" has no requirements and results in either success, which sets the path on the entity workspace, or fails and takes all it's children with it potentially telling the user or the building about the failure. Assuming there is a path though, it assigns it to the entity workspace as the state change portion of the action and then replaces itself in the queue with it's child "path to building" which can now start running, as such the entity starts following the path. Each update the "in building" check is updated and when it changes, pop "path to building" off and replace it with "create a fish" which now starts running. "Create a fish" simply puts the entity to sleep for 'x' seconds and when complete it performs "+1 fish" state change. Bingo, a chain of events broken into fairly simple concepts which can be explained to the entity with a fairly trivial algorithm. That's the very "basics" of how you can solve the "do things" portion of the problem. It is actually fairly simple though some of the code is not trivial. Where things start getting more complicated, and as Orymus3 said, how to make it work through out the game evolution, is when you start adding multiple competing needs to the entities. In Banished again, each entity has competing needs such as: stock my houses inventory, eat, sleep, procreate, get warm, get healthy, etc. At this point you start assigning priorities to the actions in each entity and as such your balancing nightmare commences. You need to start considering things like action velocity (i.e. I've started this action so it should get a boost to priority for a while before other things interrupt it), absolute interrupts such as hey that's a fricken tornado I should run away, etc. In more complicated games, Kingdom's and Castles I'm not sure of, you have multi-step production chains and things get *really* damned complicated. For instance in an older game I can't remember the name of, you had fields of wheat which you harvested, wheat was then used in a mill to produce flour, which was then taken to a bakery, which when combined with entities and other ingredients could produce different types of food. That game left a lot of the priority setting to the player and it was really difficult to get things working smoothly because the AI didn't consider time spent pathing, waiting for the mill to not be in use, etc very well when considering which actions were most efficient. Lately I believe a modified Monte Carlo Tree Search could be modified to handle such things but that's getting pretty advanced and you should probably stick to the Banished like simple model till you get a handle on some of the trickier bits. Hope I didn't trivialize this too much to make sense. If I did though and you need further expansion on bits and pieces, let me know.
  9. All8Up

    COM Interface best practices

    There is actually a rule of thumb here which is fairly easy. If you intend to supply fallback functionality for when you can not get say IDXGIAdapter4, then following point three is the best way to go. If, on the other hand, you absolutely require a certain level of the interface to exist, say you absolutely must have IDXGIAdapter3 or higher, then keep only IDXGIAdapter3 pointers. Basically there is no reason to have multiple pointers or continually query things unless you can actually handle failures and have supplied alternative paths through the code for those cases. A case in point, I'm doing work with DX12 and as such there are guaranteed minimum versions of DXGI which must exist for DX12 devices to even be created. I have no fallbacks since I can't get DX12 functional without the minimums so any failure is a terminal error. On the other hand, I do query for 12.1 features in a couple places just because they make things a bit easier, but I have fallbacks to 12.0 implementations, in those specific cases where it matters, yes I query as needed. Hope this makes sense and is helpful.
  10. There is a reason that suspend and resume do not exist in posix and many others, they are inherently unsafe. Because you don't know exactly where the thread will be during a suspend there are a lot of different things which could go wrong. For instance, if you suspend with a lock under the threads control, this can cause a very difficult to find/understand deadlock. For this reason it was deemed best to avoid this and make programmers use explicit synchronization. In general this is the best solution even if the API's are available because you know the exact state at the point of suspension, i.e. I suggest not supporting this even on Window's. See the caution here: https://msdn.microsoft.com/en-us/library/system.threading.thread.suspend(v=vs.110).aspx, and the fact that it is deprecated in .Net moving forward for these and other reasons.
  11. All8Up

    How to synchronize a Scene Graph?

    Definitely not, it's just the free threaded nature of how you described your implementation which is not the best way to go. Typically in games you perform several steps in order rather than all at once. In that manner you put 'all' threads to updating the scene graph and only when that completes do you let the render thread walk the graph to start rendering. Or, more commonly, after the update then the cull pass is run which issues the rendering commands and the render thread picks them up. Basically everything in that more common model is a 'push' towards worker threads rather than multiple items trying to process shared data.
  12. All8Up

    How to synchronize a Scene Graph?

    To be honest, this is probably the better point. But, the OP suggested a functional threading model in the description. But even with data flow controls, scene graphs are actually a problem in distribution systems and this counter approach usually ends up in both functional and job based solutions as part of the method allowing multiple threads to efficiently traverse the scene graph and not duplicate work. You could do this with OS mutex and probably perform about the same, but I've found many uses for the counter approach in areas beyond simple update and rendering, not to mention the lower memory footprint. The thing I see most often is folks pushing nodes into a job system and then each child is pushed as individual jobs. In most cases I've seen that approach it definitely utilizes all the cores, unfortunately REALLY poorly. The overhead is usually just horrible in such solutions and you'd be better off with a single threaded solution. Tree structures are always difficult to get effective parallel utilization from. The counter trick is actually one of the better ways to partition the tree's. We haven't even touched on the fact that most scene graphs have to be traversed twice, once to update all the transforms and bounds at the leaves and then again to accumulate the bounds back up to the root. That's actually a fairly simple item to solve with the approach suggested. I did forget to mention it of course. Basically you have to choose which direction you intend to perform updates and follow that direction appropriately. Let's say we choose depth first priority, if an item like the gun is locked by the renderer and it steps to it's parent to find it currently locked, it unlocks the gun and spin waits for the atomic to become 'updated'. Any time something is locking children it assumes it will always eventually get the lock so it will wait. Given that the contention is exceptionally rare in most cases, this doesn't hurt performance much and just works. This is a matter of balance and purity. If you want the render thread to do nothing but render things, you have to either remove it from scene graph traversal while others are updating the graph, or block when it runs into something which is not yet updated. On the other hand, if you don't mind that this is turning all of your threads into a more generic 'worker' concept this approach is actually an optimization. Consider that something has to do the work and the render thread would be blocked anyway, why not let it do the work to unblock itself? But, going back to Hodman's point, the approach I'm suggesting is not horrible but there are other solutions which will perform better. Though, as I mentioned, I've ended up using this as part of those other solutions anyway so I don't think it would be completely wasted work. Tagging nodes in tree's is a very common approach with parallel updates, it is common in generic concurrent tree types and many algorithm's which have complex traversals such as this.
  13. All8Up

    How to synchronize a Scene Graph?

    Believe it or not, the solution I suggested is exactly this, it just skips over all the multithreaded issues that you run into when implementing it. Threading throws a serious wrench in the mix for many obvious and simple solutions. For instance, the naive solution to implement your suggestion is a deadlock prone item in a threaded codebase. Say the rendering thread starts to render the gun, it locks the gun, the thread decides it needs to lock the parent of the gun so it tries to lock that. Unfortunately the other thread has the parent locked as it updates it and then it tries to lock the gun. Oops, dreaded deadlock issue. As I said, there are many possible solutions, even to the deadlock as described. The problem is that many of the obvious solutions are worse than just giving up on threads and running things single threaded. For a scene graph, the best solution is usually the eventual consistency model I describe simply because it avoids most cases of contention and doesn't need non-uniform work ownership rules. Amdahl's law is unforgiving, the more complexity you put into deciding what work to perform the less you will be able to utilize the threads. As such, what conceptually seems simple in this case is actually really hard to implement and eventually a single threaded approach would be better.
  14. All8Up

    How to synchronize a Scene Graph?

    There are a number of ways to approach this problem, personally I like the most simplistic variation. Basically there is a simple trick which can be used involving dependency chains and work ownership. In each of the scene graph nodes add an int32_t or anything which can be used in std::atomic's. Now, every frame, update a counter and then tell the threads about that update such that each thread will have a copy of the old and new value of the counter. At this point the obvious item is to perform an atomic compare/exchange on the counters in the nodes, if it succeeds that means the node needs to be updated before any of it's data is used. Of course there is a problem with this, if one thread succeeds in updating the counter and then starts updating the data in the node, other threads could use the data as it is being updated. So, things get a little tricky though still pretty simple. In order to fix the problem, reserve some value of the counter, I use -1 usually, which will represent a 'locked' state. Now rather than the compare/exchange just updating to the new value, it updates to the locked state first. It does the work needed to update the node and then stores the new counter value when done. Adding a little spin wait on the locked state is trivial and while it may help to do fancy exponential backoffs and such on the spin, 'usually' it is a waste of time since contention in a fair sized scene graph is pretty minimal as long as there is a decent amount of work for each thread to perform. For all intents and purposes, this approach turns your threads accessing the scene graph pretty much into a work stealing job system. The reason for that description: say your render thread wants to render the gun, it will check the counter and if it updates it, then it knows it needs to do the matrix updates. In order to do that it will track to the parent and check it's counter, if it swaps that also, it gets to perform the matrix updates for both items, i.e. stealing the work from your matrix update thread. If the matrix update thread see's already updated or locked nodes, it just ignores them since obviously some other thread is busy updating them. So long as all threads in the scene graph respect the update counter, dependencies get solved properly and everything is thread safe. At the end of the day, this system works quite well and utilizes the threads reasonably.
  15. As others have stated, that is probably not a 64bit result and you are loosing the x position, change the code as follows to guarantee things: int64_t Input::GetMouseXY() { return (int64_t(_mouseX) << 32) | int64_t(_mouseY); } Alternatively, and more difficult to get wrong or mess up later, use a combination of a union and bit fields: union MousePosition { int64_t Data; struct { int32_t X : 32; int32_t Y : 32; } Position; }; Then you can: int64_t Input::GetMouseXY() { MousePosition result; result.Position.X = _mouseX; result.Position.Y = _mouseY; return result.Data; } The bit field makes the code more verbose but also more understandable. It will do the magic of bit shifting and compositing the result for you automatically in a safe manner. (Note that in the above case, if you really want 32 bit values, you don't need the bitfield, I put it in there since I usually pack more data into that structure.) Unpacking the data is the following: MousePosition pos; pos.Data = theInt64ReturnedAbove; int32_t x = pos.Position.X; int32_t y = pos.Position.Y;
  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!