Server positioning mode of game programming mode

Keywords: Software Engineering

Provide a global access entry for a service to avoid coupling between the consumer and the specific implementation class of the service.

(from game programming mode)

For some systems, objects or functions that may be called everywhere in the game system, if they are called directly (using static class or singleton mode), it will also cause a certain coupling - when we modify the calling mode of static class or singleton mode, we need to find the code blocks of each call and modify them, Using the service locator pattern can solve this problem.

You can regard service location as a collection class of singleton pattern. In the service locator class, you can collect a large number of static instances. When we need an instance, we can get and call it through static variables.

Sample code

Simple locator class

class Audio
{
    public:
        virtual ~Audio(){}
        virtual void playSound(int soundID)=0;
        virtual void stopSound(int soundID)=0;
        virtual void stopAllSounds()=0;
}

//Simple locator
class Locator
{
    public:
        static Audio* getAudio(){return service_;}
        
        static void provide(Audio* service)
        {
            service_=service;
        }
        
        private:
            static Audio* service_;
}

Therefore, we can call the Audio instance through the Locator class. However, it should be noted that to call normally, you need to register a service provider before the game runs (call provide registration). However, simple code always has some defects. For example, in the above code, getAudio() returns nullptr when we have never registered a service provider.

Empty object mode

It is troublesome to check the return value nullptr every time the service locator is called (after all, it is not elegant to have an if for each line of call). The empty object mode can solve this problem. "Empty object" is not a real empty object, but as an object, its function will not do any operation. Here is the sample code.

class Audio
{
    public:
        virtual ~Audio(){}
        virtual void playSound(int soundID)=0;
        virtual void stopSound(int soundID)=0;
        virtual void stopAllSounds()=0;
}

//'empty object'
class NullAudio : Audio
{
    public:
        virtual ~Audio(){}
        virtual void playSound(int soundID){}
        virtual void stopSound(int soundID){}
        virtual void stopAllSounds(){}        
}


class Locator
{
    public:
        static void Initialize(){service_=&nullService_;} 
        static Audio* getAudio(){return service_;}
        
        static void provide(Audio* service)
        {
            if(sevice==nullptr)
                service=&nullService_;
            service_=service;
        }
        
        private:
            static Audio* service_;
            static NullAudio nullService_;
}

You need to execute Initialize before running the game. Even if we do not register the service provider, we will not return a nullptr.

Log decorator for service locator

We should have an updated understanding of the service locator. In essence, a service locator is a configurable singleton pattern. We can customize the development by registering different versions of German service providers. For example, in the game engine, sometimes we need to open the log, and sometimes we need to close the log to ignore some problems. How to implement it? We continue to optimize the service locator. The example code is as follows:

class Audio
{
    public:
        virtual ~Audio(){}
        virtual void playSound(int soundID)=0;
        virtual void stopSound(int soundID)=0;
        virtual void stopAllSounds()=0;
}

//'empty object'
class NullAudio : Audio
{
    public:
        virtual ~Audio(){}
        virtual void playSound(int soundID){}
        virtual void stopSound(int soundID){}
        virtual void stopAllSounds(){}        
}

//Audio module with log
class LoggedAudio : public Audio
{
    public:
        LoggedAudio(Audio& audio) : wrapped(audio){}
        
        virtual void playSound(int soundID)
        {
            log("playSound : {soundID}");
            wrapped->playSound();
        }
        
        virtual void stopSound(int soundID)
        {
            log("stopSound : {soundID}");
            wrapped->stopSound();
        }
        
        virtual void stopAllSounds()
        {
            log("stopAllSounds : {soundID}");
            wrapped->stopAllSounds();
        }

    private:
        Audio* wrapped;
}


class Locator
{
    public:
        static void Initialize(){service_=&nullService_;} 
        static Audio* getAudio(){return service_;}
        
        static void provide(Audio* service)
        {
            if(sevice==nullptr)
                service=&nullService_;
            service_=service;
        }
        
        private:
            static Audio* service_;
            static NullAudio nullService_;
}

As long as the instance of LoggedAudio type is registered with the service locator, the audio system with log can be started.

Posted by jynmeyer on Tue, 02 Nov 2021 20:43:08 -0700