Jump to content
  • Advertisement

Potatoman

Member
  • Content Count

    33
  • Joined

  • Last visited

Community Reputation

108 Neutral

About Potatoman

  • Rank
    Member
  1. Potatoman

    Alternative to singleton

    I'm curious to hear if other people share this view. Surely globals/singletons are not acceptable on the basis of whether they gracefully handle concurrent operations? Indeed new/delete come under this banner, and this has already been flagged as an undesireable on this thread. You can compress system volumes, you can encrypt them. Readfile doesn't return encrypted/compressed data, so where do you think this is being handled? I'd say about the same place it would happen on a ResourceServer (or whatever you would call it, I've never seen so much concern over a class name before). They don't serialize bytes into objects, which is why I said 'pretty much all' in my last post. Performing deserialization into objects is one of the more trivial components of a resource loader. If you cannot see how file IO API calls represent a global state, I don't really know what to say.
  2. Potatoman

    Alternative to singleton

    A thought occurred to me earlier, pretty much all of the functionality you describe above (and then some) is embedded within the windows API CreateFile/ReadFile. First let me say the windows API is not an example of good design, and that is not where I'm going with this. What I do wonder, however, is whether you mutter curses each time you have to use these API functions? Are they really all that bad, do they prevent you from getting your job done? Would you be more productive, your applications more stable/efficient/readable, if there was a different implementation provided? There are plenty of windows framework calls that genuinely do make life difficult, I just think the file IO routines are pretty low on this list. When I asked the same question on a thread earlier - how would people replace the OS filesystem (a global), nobody cared to respond. How would you go about structuring a filesystem, to access media content, while supporting concurrency, caching, decompression, decryption, prioritization, networking (among other things). Would you take all these individual components and 'pipe them all together' before going to load a file, or perhaps manage the coordination of all these subsystems in a central place, providing a common interface? On a side note - would it be fair to say you're just as opposed to any class with 'system' in the name, such as 'FileSystem' (in the same way you avoid manager)?
  3. Potatoman

    Alternative to singleton

    The situation I've dealt with in the past is the user 'equipping' a skill or weapon in an RPG, and having the need to pre-emptively load all assets that might subsequently be used for that selection. Assets are potentially shared between related scopes in this scenario, and in a heavily memory bound system like the last generation of consoles, duplicating asset data is something to be avoided. The sharing needs to happen horizontally (between siblings), not vertically between a parent/child. I can't see a safe way to achieve this type of differential loading (load scope B, but reference any shared data from sibling scope A) without some kind of reference counting mechanism. This is not to say reference counting couldn't be implemented relatively easily into the asset scope framework you have described. This may not be suitable if the memory fragmentation associated with such an approach is a problem. It was the right fit for one specific project I worked on, with load times down to a fraction of what they would otherwise be, by allowing preloading of almost all level assets during character/level select screens. It provides no data transformations, existing only as a point of reference, a collection of properties (expressed as interfaces at the entity level) defining a logical entity. It will have a tree of objects below it (chassis, wheels, whatever), but the actual control interfaces - how it translates user inputs, or whether an AI operates it, will be (optionally) defined by interfaces assigned to that root 'car' node.. or it won't have any interfaces associated with it, in which case it will basically exist as a static object. Yes, though the car could be considered a component in and of itself, having intrinsic properties (or, it might not..). If not, you might choose instead to express a car as a collection of networked (linked) entities as you describe - though some designers might find this a bit cumbersome (if they want to link another entity to the 'car', can they still link it to the root car node given it has no direct runtime representation?). I agree the loading is better done from some loading/parsing framework, completely outside of entity. Except where you start drilling down to concrete implementations, for example loading an EngineDynamicModel component or something - where data to load is a bit more private in nature.
  4. Potatoman

    Alternative to singleton

    The name is important (at least the "manager" part), and that's why "managers" are so bad. Managers tend to end up as some amorphous type with no clear responsibility (and therefore they get way too much responsibility). Managers tend to crop up when a programmer says "I've designed some different components, but now I have no clue on how to get things to work together, so I'll just throw them all in some Manager class that'll do everything for me." Which completely goes against the well established, good software practices of a) proper software engineering and design, and b) SRP. Classes have responsibilities, and ideally each class should have one responsibility. "Manager" is so incredibly vague, it's impossible to know what it does. [/quote] The functionality of the class is what counts for me, the name being helpful but largely irrelevant (as compared to class documentation). Somewhere code has to take responsibility for how to combine all these loading components together in a coherent way. In the example provided earlier this code was supplied at the site of the load call - I prefer to minimise code duplication and contain such logic in some form of manager or (stateless) utility method, depending on the situation. If I had to come up with a better name, it would be ResourceServer/AssetServer, as that's all it is doing, serving up resources for use by other components. To do this efficiently however, there is a lot that potentially goes on behind the scenes - far more than simply resolving an asset from a map. That depends on the components used by the manager, and the implementation is largely none of the client codes business (precisely how it goes about managing assets). Client code requests a resource, and receives a handle to either the loaded resource, or a resource with an associated pending/ready state. I prefer to keep this piping in a central place if it's common across your application, whether this results in a utility function to configure or a manager type object I can't say. Regardless the managers I've seen are usually pretty lightweight and have not been a source of confusion, I'm not sure what kind of managers people have been dealing with to have such strong negative feelings towards them.
  5. Potatoman

    Alternative to singleton

    This (edited) quote is the core of the "manager issue" -- BlahManager is a naming convention used for classes that violate the SRP. If you've got something that is a cache, and a priority queue, and a decompressor, then it's responsible for too many things, so it gets named a "manager"... You can have * an AssetLoader which simply accepts requests to load data from disk into RAM and fires off notifications when loads are complete. * a Decompressor which owns a shared decompression buffer and can transformed compressed streams of bytes into uncompressed streams of bytes. * an AssetCache, for determining whether a load request needs to be generated or if an existing asset can be returned. * a TextureFactory, which can take loaded/decompressed blobs of bytes and return a constructed texture. Each of the above can be written in a completely decoupled manner, so that none of them are aware of the others. The user of these classes can construct some delegates/callbacks that pipe them all together into a sensible sequence. [/quote] A manager (the precise name is unimportant) is simply the composition of the above components, its responsibility being to 'pipe them all together in a sensible sequence'. You may well already have some or all of the above components written in a decoupled manner. It's great to see some comprehensive sample code like this, it was hard to visualise what you meant by 'pipe them all together into a sensible sequence'. Can assets be shared between scopes? If I want to load the next level of my game in the background while showing tally screens or whatever at the end of the current level, how do you go about ensuring resources common to both levels are retained in memory? Can multiple scopes exist within your scene, to support dynamic (on demand) loading - and do assets in these sub-scopes correctly resolve to (and reference count) assets that are already loaded, to ensure you don't load something twice? In a scene graph (non cyclic tree) consisting of nodes, IMO entity is just the natural mapping of a single node in your scene hierarchy to an entity in code. Driven less by design and more about having a logical mapping of data nodes to code structures. There is no restriction on what may or may not be referenced in the XML of any given node, hence it knows indirectly about everything. In a direct sense it doesn't know anything, because it just has a set of abstract interfaces defining the behavior of that object, where interface implementations possibly link to other entities in the scene (objects linked in the editor). Consider a scene editor where a designer drops in a 'car' object (which is simply an XML template with predefined defaults, some of which are configurable). This 'car' is an entity, there isn't necessarily a car object representation in code, it's just an emergent property of the configuration defined in the XML. You might write helper wrappers/utility methods to simplify common operations in code, but at its core 'entity' is a collection of interfaces. This is the kind of feedback I was fishing for. Specifically where you have the map of factories passed in, as this is what I figure would be required to eliminate use of (factory) singletons without introducing a horrendous amount of interface changes. I regularly see people passing in each factory individually, which becomes a headache for maintenance and is not scaleable in the general case. When said entity stops using one of the factories, do you remove it from the interface, or leave it hanging around in case it's needed again? If the factory is optional, do you make it a pointer, or force the user to pass it in anyway? Having the map eliminates these considerations and seems to make things much clearer. If you want the responsiblities clearly defined, you can simply add it to the methods declaration comments. I agree it makes sense to move construction code out of the entity class, whenever I mention Load method, I don't mean to imply the load method is part of the interface for the object being loaded.
  6. Potatoman

    Alternative to singleton

    I think too much focus is being applied to the terminology 'context' and 'manager'. TextureLoader is a manager, a context object - they're one in the same. It provides the means to resolve a texture from a string. Call it a TextureLoader, no problem. If you start inserting procedural (runtime generated) textures into that map, TextureLoader would perhaps more accurately be called TextureResolver. Start adding load prioritization, and it starts to become more of a manager. As always I might be missing something and would welcome examples to clarify things, but how would the following requirements be handled: - Load prioritization, where a single asset potentially has multiple load requests with different priorities (requiring priority promotion) - How do you coordinate decompression where memory constraints mean a shared decompression buffer must be used. How does this shared buffer get passed around, does every load routine get passed a decompression buffer object? - What if your object wants information about load state, so it can manage LOD levels internally (switching to higher LOD once assets are available). This requires some form of LoadResource to be returned, along with load (complete) events etc. Now in terms of passing around a collection of textures directly to load methods: void Load(std::map<string, Texture>& texture_cache) ... I think this is rarely going to be sufficient on non trivial projects. For the same reason as when a level editor loads it's '.scene' file, you wouldn't pass in a std::map of all files that might be loaded by that scene.. Additionally it just strikes me as needlessly exposing implementation details. Sure you're using a map, but what if you want to change to a hashtable later. If it actually saved a nontrivial amount of time to pass around a map directly I could understand the motivation, but it could be argued the opposite is true (as in it's easier to work with a customised wrapper than a generic container).
  7. Potatoman

    Alternative to singleton

    Your posts (largely) don't read that way. They read as though your examples are sufficient justification to invalidate 'don't use singletons' as a best practice. [/quote] "Don't use singletons" is good advice, and applies in most situations. Take away all time constraints and it applies to just about every situation, perhaps all. As noted earlier I avoid absolute statements, as for something to be true in an absolute sense there cannot exist examples to the contrary. I provided examples to illustrate there exist fringe cases where you might well get by fine using them, I've seen it plenty of times in production code and they have never caused me much grief. Callback events, on the other hand.. That doesn't mean globals haven't caused other people serious grief. That said, there is a vast difference between indiscriminate/carefully considered use of global state. This is what I'm arguing (so I thought, anyway), but I have posts split across this and the globals thread - I didn't think I was being too ambiguous, but it doesn't matter what I think - I'll make an effort to be clearer in future.
  8. Potatoman

    Alternative to singleton

    What bad things? I enquired as to how one should handle various use cases, and noted you didn't care to address one of them. I didn't pass judgement, that seems more your domain. I guess there was no effort required to support multitouch, just remove a few of those pesky globals, job done. That is my point. I've given plenty of counter examples, and I could express my frustration and pretend it's your fault for 'not getting it', if I was inclined to think anyone who doesn't agree with me must have the wrong way of thinking. Yes I understand singletons can cause problems, I also understand (having seen) that they can persist quite harmlessly. If you had been following my posts you would see I make an effort to avoid them, but sometimes practical considerations (largely, though not exclusively, to do with existing code bases) become the overriding factor. A bird in the hand is worth two in the bush and all.
  9. Potatoman

    Alternative to singleton

    What happens to the concept of 'focus', do you support multiple points of focus? What happens to focus when I tab, which focus does the keyboard get routed to? Can I drag multiple rectangle select regions in my application simultaneously? I think a tremendous amount of work was saved assuming one mouse in the current architecture. That will need to change, but I'm not sure having multiple cursors is the solution. You certainly couldn't retrofit it to existing applications and expect it to be stable, given the threading interactions that could result (multiple text selections etc). In your last application that used a mouse input device - did you have a class with a mouse input device member, or a mouse input device array? When querying for mouse data, do you always iterate over the array of mice you might have, just to handle the potential there was more than one mouse was available? I just don't see the sense in half supporting something in future - you either support it (because you need it) or you don't. Having partial support for a feature means it might be a little easier to implement later (and go for it if it there is a business case doing it early), but certainly doesn't mean it will just run out of the box when that day comes. A lot of games don't support 3D head tracking natively, due to assumptions the look position is tied in with the target/aim position. I don't see that as a failing of the framework, rather a simplifying assumption that made sense at the time.
  10. Potatoman

    Avoiding Global Variables

    Design should be done for the application, not for the bug at hand. [/quote] It just strikes me as designing for the sake of design, disregarding practical considerations. Many projects simply want a log method that takes a string, nothing more. To force every method that might log data accept a logger has a very real potential to be an unnecessary burden. Hodgman made the observation that globals are design flaws. I can accept that, but sometimes a simple cout is all you want, and it serves its purpose just fine. I get the distinct impression some people will drop features before accepting 'flawed code' into their code base, even if in practical terms there is zero risk associated with that code (tag the code with a low priority 'TODO', if you must). I think that's a shame - extremes at either end of the spectrum have the potential to seriously damage a company. This is the second time I've heard a comment along these lines, which makes me wonder if I'm missing something. If you have 'n' classes to load (with their own unique data layout, being fundamentally different objects), where n is a large number - how exactly does one reduce the number of load calls below n? There is no duplication in code, it's just each class needs the option to be loaded from XML, in a largely data driven design. Fair enough, it's going to depend on your project requirements where unit test speed is a practical constraint that needs to be taken into consideration. Some projects would be happy just getting an incremental compile in under one hour.
  11. Potatoman

    fread crashes on pointer

    To avoid similar issues in future consider using static_cast, this provides the compiler with context to confirm the cast is valid, so you don't have things like this creep in by accident unsigned int val = 0xcdcdcdcd; void* ptr1 = static_cast<void*>(val); // error C2440: 'static_cast' : cannot convert from 'unsigned int' to 'void *' void* ptr2 = static_cast<void*>(&val); // ok void* ptr3 = reinterpret_cast<void*>(val); // if you insist..
  12. Potatoman

    Avoiding Global Variables

    What infrastructure? Passing a variable along isn't infrastructure and if it takes a non-trivial amount of time then you're either doing it wrong or working on some throw away code. Seriously; it takes no effort at all to do it right in the first place. [/quote] I had a specific example in mind, and I guess there are two facets to the issue. One is working with existing code, and to be fair I should probably always clarify when I'm referring to modifying an existing code base. Certainly when building from scratch it's a lesser issue - even so, logging is a prime example. For the same reasons you assert removing a singleton is painful - when I need to add some logging, I may not be able to add it in trivially because there is no log handle readily available. I might not even be sold on the idea the logging needs to stay permanently in code, perhaps I just want to do a quick test? If there's two modules between where I am, and where I need to get the logger from, I might instead opt for a less optimal solution, maybe using conditional breakpoints. Some will question 'why do you need to log there'. Seriously? I'm constrained in what I can log, and where? Logging is a design issue? Perhaps on some projects, but not the ones I've worked on. To give an example when working with existing code, the example was provided in another thread. I have a code base with hundreds if not thousands of load methods that already exist, taking an XML node in load calls. I want to add in template support, to eliminate duplication within the XML source. This is a somewhat trivial change using a global to store the template database, nontrivial (in terms of time to implement) if I need to pass in a template database to every single load call, along with everything that calls load. Now a template database using GUIDs can be considered global in every practical sense, and I'm finding it tremendously difficult to justify spending days instead of hours to inject the database lookup I need. This is a real world scenario, and a very real potential outcome is this support will never be added, if the cost benefit analysis doesn't add. No matter what the issue, you should know I always get suspicious when someone talks in absolutes, 'always', 'never'. There most certainly exist trivial cases where the rework is not more significant, and I suspect this may well be true in more complex frameworks. Nothing you, or someone, wouldn't have had to do in the first place. Refer back to my comment on logging. This is an artificial block, turn off threading for unit tests that do not support threading and make it sequential. Though I'm glad you raised this point, as it could be what many people have in mind when they talk about the difficulties of unit testing code that uses global shared data. I can't help but feel people are missing the point I'm trying to make though. I'm not advocating people use global shared data. Conversely I've seen projects run quite happily using global shared data. For every project that had serious complications through use of singletons, there are 'n' projects for which it was a non issue, I really depends on how liberally they were used. Where your project sits in the spectrum, I don't know, and I'm certainly not going to just assume (even demand) that you do things one way, and make the claim that you will regret it later if you don't.
  13. Potatoman

    Avoiding Global Variables

    I'm sure this example applies to certain companies and code bases. Not all code needs to be infinitely scaleable, indeed in some cases you know full well the code will be largely discarded in a few years time. For the same reason you may decide your code only needs to run on a single thread, use of globals may well be suitable (note the term suitable, rather than 'appropriate'). Yes, sometimes they're abused, and in cases promote poor design. Don't think you're making your programmers 'better' by restricting their toolset though. The thought process if I eliminate bad practice 'x', I'll get better quality code, is not really a great way to get the changes you would like. The result could well be even more problematic programming habits springing up.
  14. Potatoman

    Avoiding Global Variables

    If you look at both assumptions though: Assume that your code likely won't need more than one instance. You can make global. If it's global and you were right, you saved negligible time. If it's global and you were wrong, you now have a large amount of painful rework. You can make it not global. If it's not global and you were right, you spent a little bit of time to make the code more explicit (read: easier to pick-up/work with). If it's not global and your assumption was wrong, you pass in the new/different instance. [/quote] I see largely the reverse, though certainly it depends on the situation. In my experience, if you were wrong, you just wasted a potentially nontrivial amount of time on developing and supporting infrastructure you will never need. If you were right, you saved neglibigle time overall because you can go in after the fact and update it. The key benefit for building in the support is allowing rapid design changes at late stages of the project, at the cost of spending more time up front. This might be useful for a project, or it might not be. Can you give an example of a situation where the rework is more significant than simply changing the singleton to be to passed in, that is not the result of poor design in general (as in, it's a painful rework because of the general code structure, not the use of a singleton). What I mean is if we're talking about singleton vs passing in the argument, I see no difference between implementing it now or later. Why is it more painful later? At a coarse level, I imagine the 'pain' of implementing it later is proportional to the amount of time saved by making this simplifying assumption in the first place. It doesn't prevent it at all, instead of passing the parameter in, you set the global to that value. It might not be as 'clean' or as intuitive to the reader as you'd like, but I think 'prevents' is a bit strong. void TestMyContainer() { std::unique_ptr<SharedData> data(new SharedData()); MyCustomContainer c(data); c.DoSomething() } versus void TestMyContainer() { std::unique_ptr<SharedData> data(new SharedData()); MyCustomerContainer::setSharedInstance(data.get()); MyCustomContainer c; c.DoSomething() MyCustomerContainer::setSharedInstance(nullptr); } Now again with the disclaimers, I'm not recommending a container class that uses a global shared instance, that's not the point. I'm merely illustrating that it doesn't prevent you from unit testing. Please can we refrain from asking why you would actually have a container taking a shared instance in the manner demonstrated above.
  15. Potatoman

    Avoiding Global Variables

    I would argue that fewer lines of code is quicker and easier to write, and globals use demonstrably fewer lines of code. I'm not saying to use that metric to decide what approach to take, but it usually is quicker and easier - I feel it devalues your argument to suggest otherwise.
  • Advertisement
×

Important Information

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

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!