Service class: Mod Interfacing Simplified!

Handy guides on how to do things, written by users for users.

Moderators: GZDoom Developers, Raze Developers

Forum rules
Please don't start threads here asking for help. This forum is not for requesting guides, only for posting them. If you need help, the Editing forum is for you.

Service class: Mod Interfacing Simplified!

Postby Major Cooke » Thu May 27, 2021 3:06 pm

I've been receiving questions on how one can use the new Service implementation for allowing mods to rely on each other without dependencies. So I made an example to break it down, featuring a flexible spawn builder service that goes through each qualified Service object until the Get() function returns an empty string, then moves onto the next one.

The format is as follows, separated by '|':
  • Name of a replacing actor
  • Name of an actor being replaced
  • Weight of the actor

Once everything is collected, it creates a list that can be used by CheckReplacement() in event handlers.

What addons or other mods that wish to utilize a service must do, is set up a simple service with a partial name match based on what the mod author specifies. In my case, it's just called DESpawner. Any object inheriting from Service with that included in the name will be iterated through.
Code: Select allExpand view
Class DESpawnerService : Service
{
   static const String list[] =
   {      // <Replacer|Replaced|Weight >
      "DEImp|DoomImp|10",
      "DEStoneImp|DoomImp|1"
   };
   private play int Index;
   
   override String Get(String r)
   {
      Name req = r;
      
      
      if (req == 'Next')
      {
         if (Index < list.Size())
            return list[Index++];
      }
      else if (req == 'Reset')
      {
         Index = 0;
      }
      return "";
   }
}


For mod makers, the most relevant part involves using ServiceIterator and Service's Get() function, but the full example has been included for convenience. While this sample does load without errors, I've not yet tested to make sure it goes without a hitch (particularly, I'm not sure if TOK_SKIPEMPTY means delimiters are not recorded. I'm also not entirely certain if string's Replace() function treats tabs/spaces the same so the code will look a little wild).
Code: Select allExpand view
Class DESpawnerContainer play
{
   Class<Actor> Replacer, Replacee;
   Int Weight;
}

Class DESpawners play
{
   Array<DESpawnerContainer> Info;
   
   void SetupSpawners()
   {
      // Finds services by partial names.
      ServiceIterator it = ServiceIterator.Find("DESpawner");
      Service s;
      Array<String> str; str.Clear();
      Info.Clear();
      
      // Now go through and call each service that has 'DESpawner' in the name.
      while (s = it.Next())
      {
         s.Get("Reset");
         String inf = "";
         while ((inf = s.Get("Next")) && inf.Length() > 0) //Keeps going until Next returns "".
         {
            str.Clear();
            inf.Split(str, "|", TOF_SKIPEMPTY);
            
            if (str.Size() != 3) continue; //Incorrectly formatted.
            
            Class<Actor> Check = str[0];
            if (!Check)   continue; // Bad replacer.
            Check = str[1];
            if (!Check)   continue; // Bad replacee.
            int w = str[2].ToInt();
            if (w < 1)   continue; // No weight, so don't add.

            // Valid, so record it and keep going.
            let fo = new('DESpawnerContainer');
            fo.Replacer = str[0];
            fo.Replacee = str[1];
            fo.Weight = w;
            Info.Push(fo);
         }
      }
   }
}


-----

The whole system, as m8f has stated, is all about removing mod dependencies where applicable, and/or interfacing with another based on certain aspects. Not everything can be made independent, but you can create custom spawners that are flexible, or maybe even purchase menu entries with prices/requirements.
User avatar
Major Cooke
QZDoom Maintenance Team
 
Joined: 28 Jan 2007

Re: Service class: Mod Interfacing Simplified!

Postby m8f » Sun Jun 06, 2021 4:11 am

Added another take for the purpose of Service here.
User avatar
m8f
dreamer
 
 
 
Joined: 29 Dec 2017
Location: Siberia (UTC+7)
Discord: m8f#0629
Github ID: mmaulwurff
Operating System: Debian-like Linux (Debian, Ubuntu, Mint, etc) 64-bit

Re: Service class: Mod Interfacing Simplified!

Postby Major Cooke » Sun Jun 06, 2021 11:24 am

I think it'll help if you post some actual code with it too, so people know how its structured.
User avatar
Major Cooke
QZDoom Maintenance Team
 
Joined: 28 Jan 2007

Re: Service class: Mod Interfacing Simplified!

Postby m8f » Mon Jun 07, 2021 11:38 am

Done.
User avatar
m8f
dreamer
 
 
 
Joined: 29 Dec 2017
Location: Siberia (UTC+7)
Discord: m8f#0629
Github ID: mmaulwurff
Operating System: Debian-like Linux (Debian, Ubuntu, Mint, etc) 64-bit

Re: Service class: Mod Interfacing Simplified!

Postby Major Cooke » Sun Aug 01, 2021 3:03 pm

Service has recently been expanded with new functions: GetString/Int/Double/Object(UI).

This serves several purposes:
  • Reduced need for translating strings to other variable types
  • Passing, storing/retrieving objects
  • Create custom run-time functions

So if someone wants to acquire a certain object and the functions are set up correctly on the maintainer's end, you can have service members acting as artificial storage containers for performing special functions with, while not needing to cast to the specific class type. This opens up a whole new world of possibilities with mod interactions.

Furthermore, by reducing the number of string translations, you can optimize performance AND reduce erroneous results from translating from strings to numbers as an example.

I'll be updating my example above soon.
User avatar
Major Cooke
QZDoom Maintenance Team
 
Joined: 28 Jan 2007


Return to Tutorials

Who is online

Users browsing this forum: No registered users and 0 guests