Interface Problem

Started by
4 comments, last by Sneftel 14 years, 10 months ago
Hiya, I've run into a bit of a design problem, and I've never managed to find a good solution for it. I'm hoping someone can advise :) I have three parts to my system - Sounds, Sound Groups and an Audio System. The Audio System is responsible for managing an audio device, and also acts as a factory for sounds and groups. A Sound Group acts as a mechanism for controlling properties (volume, reverb) for a group of sound objects. So it's a convenience object, really :) Each sound can belong to only one group. Sounds pretty simple. My problem is in using pure-virtual interfaces. As an example, the ISound interface has a method: void ISound::SetSoundGroup( const ISoundGroup *soundGroup ); The concrete sound class needs to get a pointer to the sound group's submix buffer, to redirect the audio to it. But as it only gets given an interface, there's no way to do this. Can anyone suggest anything? Many thanks for any help :) [Edit] Thinking about it, even DirectX must have this problem. Say you call IDirect3DDevice9::SetVertexShader(). How does the device get what it needs when it's only passed a vertex shader interface - no implementation details? Hmm.. Cheers, James
Advertisement
What stops ISoundGroup from having a GetSubmixBuffer() function?
Quick reply, thanks :)

The submix buffer is an IXAudio2SubmixVoice, part of DirectX Audio.

My thinking was that I didn't want to expose parts of XAudio in the interface - there's a lot that could go wrong if the client can access/modify it. Though maybe that's incorrect.
The conventional OOP paradigm only really allows two zones of protection, "inside" and "outside" the class. (Plus protected, of course.) Java augments that with package-level protection, which kind of sounds like what you're talking about. The closest thing to that in C++ is just friending the other classes, which raises some developers' hackles but you can decide how you feel about it. Another solution is the Façade pattern, whereby the more complicated interface is hidden and a simpler interface given to the user, but as you've noticed this becomes problematic when two complicated classes need to "rediscover" each other behind their simplified interfaces. You might, however, just choose to cut that particular knot with a dynamic_cast to the full class type.
Thanks.

I'm not a huge fan of the friend keyword, but a dynamic_cast might do the trick. I'm wondering whether this would cause a performance problem if it was done ~5000 times per frame.

A method I've used before is to give each 'resource' object an ID, and have it just call the central class to do it's operations. So the previous example would be:

void Sound::SetSoundGroup( const ISoundGroup *soundGroup ){   if( !soundGroup ) { */ ... */ }   // Call the audio system to look up the actual objects and   // set the group.   m_audioSystem->SetSoundGroup( m_myID, soundGroup->GetID() );}


I end up having huge central classes though, whatever they might be - renderer, audio system, etc where this applies. It's also pretty bad in terms of encapsulation and seperation of responsibilities.

Sneftel, your advice always seems sensible. If I can ask, how would you lay this out?

Appreciate the help!
Quote:Original post by beebs1
I'm not a huge fan of the friend keyword, but a dynamic_cast might do the trick. I'm wondering whether this would cause a performance problem if it was done ~5000 times per frame.
It might. Only one way to find out. (Okay, two ways. But only one way that won't enrage PETA and significantly deplete the world's supply of molybdenum.)

Quote:A method I've used before is to give each 'resource' object an ID, and have it just call the central class to do it's operations.
I've done something like this before, and for much more trivial reasons. It's rather the tail wagging the dog, though, isn't it? After all, the only thing you can really do with an ID (other than membership/equality stuff) is go back to the object type, so you're essentially making a full circle object->ID->object, and wasting cycles on that. There are reasons to identify objects by ID, but this doesn't seem to fit that pattern.

If you want to do it in a really OOP-tastic way, your AudioSystem can have a std::map<ISound*, SoundImpl*> that remembers all the sounds it's created and will cast those (and only those) to the implementations it knows they actually refer to. I wouldn't recommend this, for the same reason I wouldn't recommend the ID approach.

Now, you're using ISoundGroup to hide SoundGroupImpl, but you aren't actually leveraging the flexibility that gives you, namely the ability to have multiple classes implementing ISoundGroup. If you like, then, you can simply have a SoundGroupImpl* GetImpl() virtual method in ISoundGroup, which is implemented in SoundGroupImpl as return this. That at least will limit the efficiency hit from dynamic_cast (which is often slower than virtual function calls, but never faster). Now, doing this is bad juju for two reasons, of varying importance. You've got a big ugly IIIMPLEMENTATION DEEETAIL (read that in a spooky, foreboding voice) fully visible to users, so you might want to rename it GetImpl__UNSAFE_DONOTUSE(). The other reason, of course, is that this bars you from ever actually using the polymorphism you're spending those virtual function calls on. You're already denying yourself that ability through the dynamic_cast you've got going, though, so maybe you don't mind.

Quote:If I can ask, how would you lay this out?
With Python. [smile] That's probably not real helpful.

This topic is closed to new replies.

Advertisement