Jump to content

  • Log In with Google      Sign In   
  • Create Account


Alternative to singleton


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • This topic is locked This topic is locked
65 replies to this topic

#41 Olof Hedman   Crossbones+   -  Reputation: 2665

Like
0Likes
Like

Posted 27 March 2012 - 11:29 AM

Sure, there are cases where two mice would be nice, nothing wrong with my imagination (btw, I even mentioned the 3d editor case...)

I still say though, that the main reason you don't have it in the "main ui", is a user experience design decision, more then technical.
It's just not useful _enough_ for normal user use.
Also remember that most people are pretty dominant on one side, and have great difficulties to use their hands independently.
I'd say it's much harder to present how to use it, and its benefits, to the user, then it is to implement in the system
(power users like us don't count unfortunately, we're too few)

Personally, I think mice in general suck as an input device (too unergonomic) and would like to do away with the mouse completely, but thats a bit too sci fi for today...

Sorry, I'll stop derailing the thread now :)

The on topic point you are making, I agree with.
I just got hung up on the mouse example for some reason.

Sponsor:

#42 Telastyn   Crossbones+   -  Reputation: 3724

Like
1Likes
Like

Posted 27 March 2012 - 11:49 AM



I'm kinda shocked at the lack of design pattern proponents in this thread.


Mildly blind obedience to design patterns is a significant reason that singleton overuse exists in the first place.



Yes, but ignoring them is throwing the baby out with the bathwater.


Indeed, but their use in an argument against something that they once promoted seems... hypocritical.

#43 Potatoman   Members   -  Reputation: 108

Like
1Likes
Like

Posted 28 March 2012 - 09:42 AM

a singleton in mostly every os of today: the mouse.

if it wouldn't be, we could plug in more than one mouse and use more than one cursor (similar to multitouch) since years in every os. and heck yes, i would. sadly, at some point we got limited to only one device ever, as no one ever needs more than one cursor. and the whole os' got designed around it.

now that we can have multitouch, the os' are still not designed for it (at least win7). the result: i can not, for example, drag multiple windows independently around the desktop at the same time.


it's a simple example of potential that was there since years and never got exploited just because we where all fixed on "there can be only one".


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.

#44 davepermen   Members   -  Reputation: 1007

Like
4Likes
Like

Posted 28 March 2012 - 10:28 AM

And there you see for yourself the wrong way of thinking that is the singleton drug: See all the bad things.

Guess what? For multiTOUCH, that mindset had to change (example in win8: with one finger you chose a tile, with the other you move the full screen below that tile to quickly bring it to a new place). so would it had to change for multimouse, obviously.

and yet, all the games and apps on multitouch devices don't HAVE to care about multitouch if they don't want to. but they CAN

and THAT, sir, is the POINT.

SINGLETONS RESTRICT POSSIBILITIES.

THAT
IS
NEVER
A
GAIN

NEVER.

i gave you multiple examples by now, and yet you just don't get it. it's like someone who smokes who just can not understand what to do if he would quit smoke, as so much would change to the worse (no breaks all the time, because, what to do then? getting fat, because one eats more, etc). you know singletons are bad, yet you don't even try to just work and think without them. try it.
If that's not the help you're after then you're going to have to explain the problem better than what you have. - joanusdmentia

My Page davepermen.net | My Music on Bandcamp and on Soundcloud


#45 Potatoman   Members   -  Reputation: 108

Like
1Likes
Like

Posted 29 March 2012 - 08:28 AM

And there you see for yourself the wrong way of thinking that is the singleton drug: See all the bad things.


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.

Guess what? For multiTOUCH, that mindset had to change (example in win8: with one finger you chose a tile, with the other you move the full screen below that tile to quickly bring it to a new place). so would it had to change for multimouse, obviously.

and yet, all the games and apps on multitouch devices don't HAVE to care about multitouch if they don't want to. but they CAN

and THAT, sir, is the POINT.


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 gave you multiple examples by now, and yet you just don't get it. it's like someone who smokes who just can not understand what to do if he would quit smoke, as so much would change to the worse (no breaks all the time, because, what to do then? getting fat, because one eats more, etc). you know singletons are bad, yet you don't even try to just work and think without them. try it.


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.

#46 Cornstalks   Crossbones+   -  Reputation: 6966

Like
1Likes
Like

Posted 29 March 2012 - 09:02 AM


Guess what? For multiTOUCH, that mindset had to change (example in win8: with one finger you chose a tile, with the other you move the full screen below that tile to quickly bring it to a new place). so would it had to change for multimouse, obviously.

and yet, all the games and apps on multitouch devices don't HAVE to care about multitouch if they don't want to. but they CAN

and THAT, sir, is the POINT.


I guess there was no effort required to support multitouch, just remove a few of those pesky globals, job done. That is my point.

You're sure it was that easy?
[ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

#47 Telastyn   Crossbones+   -  Reputation: 3724

Like
-1Likes
Like

Posted 29 March 2012 - 10:14 AM

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.


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.

And frankly, the design scenarios presented aren't even close. Existing codebases and time pressure will prevent best practices, but again, you don't seem to be arguing that point.

#48 Potatoman   Members   -  Reputation: 108

Like
3Likes
Like

Posted 30 March 2012 - 09:53 AM


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.


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.


"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.

Existing codebases and time pressure will prevent best practices, but again, you don't seem to be arguing that point.


This is what I'm arguing (so I thought, anyway), but I have posts split across this and the globals thread -

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


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.


It goes a little deeper than simply "it's easier". Sometimes whether a feature gets implemented hinges on the associated cost benefit analysis, it may not feasible to touch code all over the place to get in what is, at a fundamental level, a trivial 10 line change


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.

#49 kloffy   Members   -  Reputation: 870

Like
3Likes
Like

Posted 30 March 2012 - 04:26 PM

I have enjoyed reading this thread, but it seems that it is more and more heading in the direction like most singleton threads: everybody is throwing in their reasons for why singletons are bad. That was never really an issue to begin with. The thread starter asked for an alternative.

The reason that these threads always end up this way is probably because there is no simple answer. To make things worse, it is difficult to reduce it to a simple problem. Whenever I try to think of a example, it either does not capture the entire problem or it becomes too complex. I kind of liked the example of the Entity class, which needs to load various types of Resources. Do you pass every ResourceManager to the Entity constructor? Do you group several ResourceManagers together into a LoadingContext and pass that? Is that really an improvement?

Ultimately, I think we all agree that there are better solutions than singletons. The problem is that unfortunately there isn't one solution. Some people would like to hear: "instead of singletons always pass parameters". If you were to naively follow such advice, your code quality would most likely not improve. I myself would love to find such a simple do-it-all solution, which is why I still read these threads. Unfortunately, I will probably never find it...

#50 Antheus   Members   -  Reputation: 2393

Like
1Likes
Like

Posted 31 March 2012 - 07:01 AM

I kind of liked the example of the Entity class, which needs to load various types of Resources. Do you pass every ResourceManager to the Entity constructor? Do you group several ResourceManagers together into a LoadingContext and pass that? Is that really an improvement?


Why does everything need to be a class? Why do there need to be managers and contexts.

std::map<string, Texture> texture_cache;

template < class T, class Loader >
void loadWithCache(std::string filename, std::map<string, T> & cache, T & default = T::getSingleton()::getInstance()) // look, a singleton
{
  if (cache.fine(filename) == cache.end()) {
	T t = Loader(filename);
	if (failed(t))
	   t = default;
	else
	 // put into cache
  }
  return t;
}

The state is passive. A cache is boring map, no matter how many decorators you put around it, it's a boring map which maintains key-value pairs.

Caching is a universal concept. It's not something that applies to Xmanager of Xcontext or somesuch. Caches are all over the place. The above approach generalizes it once and is done with it.

For testing, plug in any Loader or dummy cache.

The singleton.... Sure, why not. We have a missing resource. It doesn't need to be a singleton, it just goes to show where the misunderstanding is. Singleton is a value type, like number 1. The fact it's allocated as a singleton is completely irrelevant, we just choose to allocate it as such but don't care - anyone can override.

Few if any classes, prefer free functions.

To extend the cache - just add more free functions.

And if you care about reusability - just copy paste the function. There are no hidden dependencies inside, it's all defined by its interface.

Ok, let's say that gets clumsy and we want to handle it in a simpler way. We have closures and lambdas and whatnot, but we can abstract the above as well:
struct TextureLoader {
  std::map<string, Texture> * source;
  Texture * default;
  void operator()(std::string filename) {
	 return loadWithCache(filename, *source, *default);
  }
}
Now you have all of the above conveniently wrapped into something that can be passed around, perhaps into "Entity" class or such. But the wrapper is light, carries no real state and can be adjusted and modified as needed, its interface is absurdly simple:
wrapper(filename_to_load);

In other languages and in upcoming C++, there are built-in facilities that support this better at language level.

I myself would love to find such a simple do-it-all solution, which is why I still read these threads. Unfortunately, I will probably never find it...


It's been published 5 years ago and is being used in production for much longer than that. It really is no rocket science.

The solutions mostly aren't "simple", since problems being solved are quite hard.

#51 kloffy   Members   -  Reputation: 870

Like
0Likes
Like

Posted 31 March 2012 - 01:03 PM

Thank you for the link to "The Clean Code Talks". I have been looking at Dependency Injection/Inversion of Control since you first mentioned it in your earlier posts. I am surprised that I haven't come across this concept/pattern before. It certainly looks like a nice and simple recipe that will address all sorts of problems. I will keep it in mind when I structure my next project. It sounds very promising, I am looking forward to see how it works out in practice.

Btw.: I have found Dependency Injection confusing at first due to the complexity of the popular frameworks. If you need reassurance that the core concept is simple, read Dependency Injection Demystified.

#52 Potatoman   Members   -  Reputation: 108

Like
0Likes
Like

Posted 09 April 2012 - 05:55 AM

Why does everything need to be a class? Why do there need to be managers and contexts.


Ok, let's say that gets clumsy and we want to handle it in a simpler way. We have closures and lambdas and whatnot, but we can abstract the above as well:

struct TextureLoader {
  std::map<string, Texture> * source;
  Texture * default;
  void operator()(std::string filename) {
	 return loadWithCache(filename, *source, *default);
  }
}
Now you have all of the above conveniently wrapped into something that can be passed around, perhaps into "Entity" class or such. But the wrapper is light, carries no real state and can be adjusted and modified as needed, its interface is absurdly simple:
wrapper(filename_to_load);

In other languages and in upcoming C++, there are built-in facilities that support this better at language level.


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).

#53 Hodgman   Moderators   -  Reputation: 27908

Like
4Likes
Like

Posted 09 April 2012 - 06:19 AM

Start adding [more responsibilities], and it starts to become more of a manager.

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.


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).

In my engine, the cache/map is a custom class called an AssetScope. The lifetime of assets are bound to the lifetime of an AssetScope (i.e. when an asset scope is destructed, all the assets that were loaded using it are also destructed), so in order to load a scene, you would have to make an AssetScope for that scene. The implementation of the AssetScope is hidden (it's a hash table), but the reason for using a custom container here is simply because my asset loading occurs across many threads concurrently, so std::map/etc aren't valid.

My engine isn't as decoupled as the above example because the AssetScope explicitly knows what a BlobLoader is (and vice versa, which isn't great), but it's close -- Assets are just handles that don't know the type of their data, and (besides the above flaw) AssetScopes/Factories/Loaders/Decompressors are decoupled.
e.g.[source lang=cpp]struct Asset : NonCopyable{public: bool Loaded() const { return !!m_handle; } Handle GetHandle() const { return m_handle; }protected: Handle m_handle;};class AssetScope{public: AssetScope( ScopeAlloc& a, uint maxAssets, AssetScope* parent=0 ); template<class F> Asset* Load( AssetName n, BlobLoader& blobs, F& factory );};struct BlobLoadContext{ AssetScope* scope; AssetStorage* asset; void* factory;};class BlobLoader{public: struct Request { typedef void* (*PfnAllocate)(uint numBlobs, u32 blobSize[], u8 blobHint[], BlobLoadContext*); typedef void (*PfnComplete)(uint numBlobs, u8* blobData[], u32 blobSize[], BlobLoadContext*); BlobLoadContext userData; PfnAllocate pfnAllocate; PfnComplete pfnComplete; }; void Load(const AssetName&, const Request&); void Update();//polls requests to fire callbacks};class ShaderPackFactory : public BlobFactory<ShaderPackFactory>{public: ShaderPackFactory( GpuDevice& dev );private: void Acquire( uint numBlobs, u8* blobData[], u32 blobSize[] ); void Release( u8* blobData );};...m_blobLoader = eiNew(a, BlobLoader)( a );m_assetScope = eiNew(a, AssetScope)( a, scene.maxAssets );m_effectFactory = eiNew(a, ShaderPackFactory)( *m_gpuDevice );m_effectPackAsset = m_assetScope->Load( myAssetName, m_blobLoader, *m_effectFactory );...if( m_effectPackAsset.Loaded() ) m_effectPack = *m_effectPackAsset.GetHandle();[/source]

I have enjoyed reading this thread, but it seems that it is more and more heading in the direction like most singleton threads: everybody is throwing in their reasons for why singletons are bad. That was never really an issue to begin with. The thread starter asked for an alternative.

Either pass parameters to functions that would make use of Singletons, or pass parameters to those object's constructors (or similarly store them in other context/state objects) and cache the pointer for use later in said functions. It really is that simple -- the rest is now a problem of refining your software engineering craft to reduce the amount of dependencies in your code-base. When using Singletons, your dependencies are invisible, so you don't realise how bad your code is, and when you switch to parameter passing without a redesign, an excess of parameter passing is simply showing you the amount of dependencies that you've been creating all along.

I kind of liked the example of the Entity class, which needs to load various types of Resources. Do you pass every ResourceManager to the Entity constructor? Do you group several ResourceManagers together into a LoadingContext and pass that? Is that really an improvement?

That's a bad example because you're trying to rescue an already bad design. What is this Entity class that has to know about every resource type anyway? That's the reason it's hard to fix... What's the responsibility of an entity? Is it to manage the lifetime of it's member resources?
I digress... If the entity has to be responsible for resource creation, then you can pass in a directory of factories into the entity.
struct Entity { Entity( const map<ResType, Factory*>& ); }
Another solution is to move resource creation out of the Entity and have other modules put resources into an entity.
struct Entity { void AddMember( void* widget, void (*onDelete)(void*) ); };
...but again, the best solution depends on exactly what the responsibility of an "entity" is, so you'd have to define that first.

#54 swiftcoder   Senior Moderators   -  Reputation: 9658

Like
5Likes
Like

Posted 09 April 2012 - 07:26 AM

I have enjoyed reading this thread, but it seems that it is more and more heading in the direction like most singleton threads: everybody is throwing in their reasons for why singletons are bad. That was never really an issue to begin with. The thread starter asked for an alternative.

The alternative is to design your program properly. Singletons aren't even a code problem, they are a conceptual problem on the part of the programmer.

If you don't currently need multiple copies of a given class, why would you assume that you should put in extra work to ensure that there can never be multiple copies? That is the core design question. And the only possible case where it makes sense to delve into singletons, is in the face of a hardware resource that is explicitly dangerous to access from multiple points.

I kind of liked the example of the Entity class, which needs to load various types of Resources.

Your design is already broken. Why would the Entity class need to load things? That is the responsibility of an EntityLoader, or an EntityFactory (or more likely, a cluster of related SpecificEntityFactory).

Some people would like to hear: "instead of singletons always pass parameters". If you were to naively follow such advice, your code quality would most likely not improve.

Unfortunately for those people who are not using their heads, that is exactly the solution. The chain of thought looks a little like this:
  • Why did these people use singletons in the first place? Because they were tired of passing so many parameters around.
  • Why were they passing so many parameters around? Because they had poor design.
  • How do you fix poor design? You refactor to ensure single responsibility (and potentially, a number of other basic design rules).

Tristam MacDonald - Software Engineer @Amazon - [swiftcoding]


#55 Potatoman   Members   -  Reputation: 108

Like
0Likes
Like

Posted 09 April 2012 - 08:34 AM

Start adding [more responsibilities], and it starts to become more of a manager.

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.


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.

In my engine, the cache/map is a custom class called an AssetScope. The lifetime of assets are bound to the lifetime of an AssetScope (i.e. when an asset scope is destructed, all the assets that were loaded using it are also destructed), so in order to load a scene, you would have to make an AssetScope for that scene. The implementation of the AssetScope is hidden (it's a hash table), but the reason for using a custom container here is simply because my asset loading occurs across many threads concurrently, so std::map/etc aren't valid.


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?

That's a bad example because you're trying to rescue an already bad design. What is this Entity class that has to know about every resource type anyway? That's the reason it's hard to fix... What's the responsibility of an entity? Is it to manage the lifetime of it's member resources?


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.

I digress... If the entity has to be responsible for resource creation, then you can pass in a directory of factories into the entity.
struct Entity { Entity( const map<ResType, Factory*>& ); }
Another solution is to move resource creation out of the Entity and have other modules put resources into an entity.
struct Entity { void AddMember( void* widget, void (*onDelete)(void*) ); };
...but again, the best solution depends on exactly what the responsibility of an "entity" is, so you'd have to define that first.


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.

#56 Cornstalks   Crossbones+   -  Reputation: 6966

Like
2Likes
Like

Posted 09 April 2012 - 09:26 AM

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.

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.

Look at "AssetManager". What does it do? It... uh... manages assets... but... how? What does it really actually do? Does it load assets? Does it store them in a cache? Does it free them when they're no longer in use? Does it just load raw bytes, or does it construct objects from the loaded raw bytes? If it constructs objects, what kind of objects? There are lots of different kinds of assets. What if I want to load compressed data; can it decompress? If so, what compression formats does it support? None of these questions, and many others, are clear when using these amorphous manager classes. That's what's so nice about Hodgman's list of classes with a single responsibility: it's very clear what the responsibility of each one is, and what the capabilities of each class are. "Manager" just says it manages, but in reality every class "manages" its members, so what makes this "Manager" class special? What does it actually do? No one really knows, because "Manager" classes are just classes people create to throw responsibilities they aren't sure how to deal with into some pile. And no, I'm not saying I'm a programming god and I've never used a "Manager" class before. Of course I have. I think we all have, at some point. And that's exactly why I hate them so much.

And the "pipe them all together in a sensible sequence" doesn't need to happen in a class, even. Like Hodgman said, they could easily use callbacks/delegates to handle the "piping." No need for a superfluous class (a.k.a. "manager").

I didn't mean to go off on managers so much, but that's a big area I see singletons being used: in amorphous managers. The managers and the singletons are part of the same problem: poor software engineering.
[ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

#57 Hodgman   Moderators   -  Reputation: 27908

Like
4Likes
Like

Posted 09 April 2012 - 10:11 AM

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?

A bit of background first -- working on consoles with proprietary engines, I've often seen the available RAM be partitioned into a few large allocations first, and then each of these large 'chunks' is given to a different sub-allocator.
For example, on a platforming/adventure game, we'd create three 'chunks' for the level to use, which would be assigned to geographical chunks of the world. You'd have two valid chunks at any one time, and a 3rd one streaming in. When you move to the 3rd zone, the first zone's memory is reclaimed (at no cost - everything is placement new'ed + leaked) and given to the next streaming zone.
A 4th chunk would be used for things that are permanent across zones, like the player and their HUD. If you wanted to share a resource across zones, you'd either put it in this 4th chunk, or you'd memcpy from chunk to chunk during the streaming process.

Likewise, I can create several AssetScopes with different lifetimes (e.g. some with "level-chunk" lifetime, and some with "whole-level" lifetime) and I can load an asset into an AssetScope by copying it from another scope, instead of loading from disk.

If two scopes have completely different lifetimes, they cannot share their contents without duplication/memcpying. Ref-counting is not used for most assets.
You can however form a parent/children hierarchy of scopes that can share assets up the tree. E.g. if a "level-chunk" scope was parented under a "whole-level" scope, then if you asked the "level-chunk" scope to load an asset that already existed in it's parent, then the asset would not be reloaded (it would be fetched from the parent). This is safe because the children are assumed to have a shorter lifetime than the parent (in the exact same way the the C++ local-variable-stack / call-stack works -- locals further up the call-stack have longer lifetimes than locals in the current function).

For some more background, the AssetScopes themselves are allocated using something akin to Dice's Scope Stacks (my current engine has a "no globals" policy, so new/delete/malloc/free aren't allowed to be used, as they're basically a singleton memory allocator). This actually makes it very easy to write code in such a way that it's guaranteed that child AssetScopes are deleted before their parents -- as long as they're allocated afterwards, then they'll go out of scope first (you don't need to delete or ref-count anything when using ScopeStacks).

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.

What processing operations / data transformations does the entity node provide? In this case, if it's only required to group sub-objects in the editor, then it doesn't need to exist in the game -- the car will emerge simply by loading the actual components that were specified under the car node, without creating a representation of the car node itself.
Just because your XML is a "scene graph" of nodes, that doesn't mean that your de-serialised XML data has to follow the same structure. You could dump all the car-components into the scene and connect them to each other without having an node retaining links to them.

There may be a different answer to this that is more relevant to you, but that requires the purpose of the entity to be defined. For example, if you said "the gameplay programmer wants to be able to find the collision-mesh underneath the "Car01" entity node", then the purpose of an entity turns out to be a dictionary of class-types mapped to instances of those Classes, which can exist within a dictionary that maps names to entities...

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?

Passing the map of factories into the entity only makes sense if the Entity's purpose for existing is "parsing XML files". In that case, then before parsing an XML file, I'd construct a map containing all of the factories that your scene XML files could possibly require, and use that map during any deserialisation routine. However, in this case, I'd move this logic out of the Entity class altogether and into an XmlSceneParser instead... and again get rid of the Entity as a misplaced concept, or find it's real purpose and make it do that.

#58 ApochPiQ   Moderators   -  Reputation: 14305

Like
1Likes
Like

Posted 09 April 2012 - 10:39 AM

Goddamnit, Hodgman. Every time I want to close this thread, you go and make good points.

#59 Potatoman   Members   -  Reputation: 108

Like
0Likes
Like

Posted 10 April 2012 - 08:23 AM


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.

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.


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.

Look at "AssetManager". What does it do? It... uh... manages assets... but... how? What does it really actually do? Does it load assets? Does it store them in a cache? Does it free them when they're no longer in use? Does it just load raw bytes, or does it construct objects from the loaded raw bytes? If it constructs objects, what kind of objects? There are lots of different kinds of assets. What if I want to load compressed data; can it decompress? If so, what compression formats does it support? None of these questions, and many others, are clear when using these amorphous manager classes.

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.

And the "pipe them all together in a sensible sequence" doesn't need to happen in a class, even. Like Hodgman said, they could easily use callbacks/delegates to handle the "piping." No need for a superfluous class (a.k.a. "manager").

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.

#60 Potatoman   Members   -  Reputation: 108

Like
0Likes
Like

Posted 10 April 2012 - 08:52 AM

If two scopes have completely different lifetimes, they cannot share their contents without duplication/memcpying. Ref-counting is not used for most assets.
You can however form a parent/children hierarchy of scopes that can share assets up the tree. E.g. if a "level-chunk" scope was parented under a "whole-level" scope, then if you asked the "level-chunk" scope to load an asset that already existed in it's parent, then the asset would not be reloaded (it would be fetched from the parent).


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.

What processing operations / data transformations does the entity node provide? In this case, if it's only required to group sub-objects in the editor, then it doesn't need to exist in the game -- the car will emerge simply by loading the actual components that were specified under the car node, without creating a representation of the car node itself.

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.

Just because your XML is a "scene graph" of nodes, that doesn't mean that your de-serialised XML data has to follow the same structure. You could dump all the car-components into the scene and connect them to each other without having an node retaining links to them.

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?).

Passing the map of factories into the entity only makes sense if the Entity's purpose for existing is "parsing XML files". In that case, then before parsing an XML file, I'd construct a map containing all of the factories that your scene XML files could possibly require, and use that map during any deserialisation routine. However, in this case, I'd move this logic out of the Entity class altogether and into an XmlSceneParser instead... and again get rid of the Entity as a misplaced concept, or find it's real purpose and make it do that.


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.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS