Service interface for cross-mod communication

Wed May 13, 2020 6:05 am


This add interface for cross-mod communication. This is a means to make optional mod dependencies.

Service class provides a single virtual method, String get(String request), which can be overridden in Service implementation. Then, clients will be able to find that service via ServiceIterator.

Usage example is provided in the PR.

Re: Service interface for cross-mod communication

Thu May 28, 2020 11:27 pm

There is a discussion on this pull request on the GitHub, I'm putting it here too, so maybe it may help me get more insight into the problem. Until I understand the issue, I cannot adapt the code to fit the developers' requirements.

edward-san wrote:Interesting. How does this approach cope with multiplayer mode, where clients may have different loaded mods for various purposes, like graphics and etc.?

m8f wrote:This is an alternative to cross-mod communication via EventHandler.SendNetworkEvent. If the data that is passed between mods affects the game, mod authors have to use EventHandler.SendNetworkEvent.

Graf Zahl wrote:So in other words: This completely bypasses the network barrier?
You wouldn't expect modders to cluelessly use this and make their mods incompatible with multiplayer and demo recording?

Sorry, but we set it up this way so that modders won't accidentally create such mods - of course nobody will ever stop them from deliberately screwing things up - but I don't think it's a good idea to provide ready-made solutions to do such things.

Graf Zahl wrote:If it can cross the network barrier it will completely circumvent all the protections we added to the engine to ensure that modders won't accidentally create mods that break demos and multiplayer, so yes, it should be restricted to one side of it.

m8f wrote:Honestly, I don't see how this introduces a new problem.

S1. Players involved in a netgame can load different sets of mods. Suppose that Player1 has a mod that Player2 doesn't have. Then if this mod changes the game, players will desync. I just checked this, it works like this. There is no protection from this situation besides "out of sync with..." message.

S2. If players have the same service-client combination, then service and client will behave the same, both service-client pairs working on their own side of network barrier. They don't "bypass the network barrier".

S3. If players have different service-client combinations:
S3.1. servers cannot cause different changes in the game because they are data-scoped. Even if they could, see S3.2.
S3.2. clients may behave differently depending on the supplied data from server (or lack of such information). This is an issue, of course, but it's the same issue that is described in S1, which already happens without Services.

If I'm wrong, please correct me. I feel that I don't understand something here. If you have time, please explain. I also will be glad if I'm pointed to a source where I can read on the subject.

Re: Service interface for cross-mod communication

Fri May 29, 2020 3:24 am

As long as there's no way to share data between Play and UI outside of SendNetworkEvent then there is no issue. I think Graf's biggest concern is precisely that - if there's a way for the Play scope to access UI data, then this is no bueno.

I haven't looked at it but I can't confirm whether this is the case or not.

The idea is - the UI scope is supposed to be volatile - things can change in it that absolutely cannot be depended on and also do not get synchronized across all network nodes (including demo playback).

The play scope is supposed to be the complete opposite - the data is supposed to be reliable, dependable, and should only be able to change with precise algorithmic calculations that occur every tic and depend on data that you can rely on being sync'd (i.e. network events, player input states).

Requiring players to all have the same mod (unless it's UI-only mods) is a given.

If this is the case with this submission and it follows my explanation here, then it would be good to just let him know.

Re: Service interface for cross-mod communication

Sun May 31, 2020 2:03 am

Thanks, Rachael!

Now I get it, indeed, there was an issue with scoping - it was in Data scope, which is accessible from both Play and UI. I added proper scoping now.

Re: Service interface for cross-mod communication

Mon Aug 03, 2020 1:40 pm

Merged into QZDoom for testing.

Re: Service interface for cross-mod communication

Sat Oct 31, 2020 10:51 am

So M8F, can you give me an example on how I would implement a check for what I need to disguise a monster's maximum health? I have a system set up where monster health can be scaled based on an option slider.

Also, if I'm to understand this correctly, there's a play side and a UI side to this now?

Re: Service interface for cross-mod communication

Sat Oct 31, 2020 12:44 pm

First, to benefit from this system, you need two mods (if there is a single mod, the information can be passed directly). And:
- you want to be able to load these mods separately;
- if loaded together, you want them to communicate.

Say, mod A provides maximum monster health modification, and mod B wants to know the new maximum monster health.

In mod A you define a Service that looks like this:
class MaxEnemyHealthService : Service
  override string get(string enemyClassName)
    int newMaxHealth = calculateNewEnemyHealth(enemyClassName);
    string result = string.format("%d", newMaxHealth);
    return result;

where calculateNewEnemyHealth is whatever math you have.

in mod B, where you want to know if the maximum monster health is modified, and what the new value is, you look if the service exists:
  ServiceIterator i = ServiceIterator.find("MaxEnemyHealthService");

  if (!i.serviceExists())
    // use default maximum monster health

  Service s;
  while (s = i.Next())
    string maxHealthData = s.get(monster.getClassName());
    int maxHealth = maxHealthData.toInt();
    // do something with maxHealth.

Note that somebody else may create another version of mod A, say, mod A2, and you have to deal with several information providers. Or mod A could subclass MaxEnemyHealthService several times.

Regarding scope, yes, there are two variants of get: in play and UI scope. Use one that is appropriate for your data usage. Purely cosmetical - use UI. Modifies gameplay - use play.

Re: Service interface for cross-mod communication

Sat Oct 31, 2020 4:12 pm

Based on what you're saying, and from what you said in GitHub:

M8F wrote:Yes, your mod could add a Service that would return proper spawn health for a class. Target Spy then could add code that checks for Service existence and asks it for health value.

It sounds like mods like TargetSpy would be Mod B. Any other mod that wants to incorporate the ability to disguise the max health would be Mod A, say for example my Triple M mod.

If this is true, since Mod A has to define the MaxEnemyHealthService, then multiple mods will have class name conflicts. This is a problem and will prevent mods from loading together. What I think there should be instead is a string that sets the actual name of the service so there's no collision. People can name the class whatever they want like "D4MaxEnemyHealthService", call a setup function that sets up the name "MaxEnemyHealthService" as an identifier and go from there.

Otherwise, if people have to rename their classes in order to provide the service for TargetSpy, it's work on both of the modders to handle. You'll have D4MaxEnemyHealthService, MCMaxEnemyHealthService, etc... It can quickly stack up, and it can get ugly.

Re: Service interface for cross-mod communication

Sat Oct 31, 2020 4:39 pm

Check how it implemented on Github.

It collect all successor of Service class before calling redefined Get/UIGet virtuals of each. Class name conflict happens in same cases as with "default" Zscript, if both mods have classes with same name.
Also, since it return strings only its a duty of a modder who want to use them to return it formatted in some way, for example for TargetSpy "my_cool_target_spy_mod_subscribe_to_my_twitter::current_enemy_health_multiplier_is_equal_to::"..cvar.getcvar(mon_hel_mult_fr_intermod_compat_usn_srv_cls")..", which then should be split on :: to get "mod identifier::variable name::variable value".
And you should check, in ServiceIterator.Next(), what service from what mod with what value it currently returned.

Re: Service interface for cross-mod communication

Sat Oct 31, 2020 4:59 pm

Doing it by inheritance means that it relies upon the other mod being there, and that won't work at all.

Re: Service interface for cross-mod communication

Sat Oct 31, 2020 5:01 pm

Inheritance from Service class I meant. It would be included in gzdoom.pack, so it would be there in any case.

Re: Service interface for cross-mod communication

Sat Oct 31, 2020 5:05 pm

yes, but that's exactly the problem. If you want to make it inherit from type MaxHealthGetter defined in TargetSpy so it's taken into account, that means any mod that implements:

Class D4MaxHealthGetter : MaxHealthGetter

...become reliant upon that mod being present at all times. Sure, could just inherit from Service directly, but specifying a name to search it by means it's basically going to skip over it, unless it's used for, say, 'private' service classes.That works fine for things such as libraries, sure, if that's what its intended use case is.

Re: Service interface for cross-mod communication

Sat Oct 31, 2020 5:11 pm

Eeeee.....looks like we have problems understanding each other.
Just wait for m8f, he should explain it better than me.

Re: Service interface for cross-mod communication

Sun Nov 01, 2020 4:08 am

Major Cooke, you are right. I have to think about a way to service provider mods not to conflict. I'll use your idea with setting actual service name if I don't come up with something more convenient.

Re: Service interface for cross-mod communication

Sun Nov 01, 2020 1:22 pm

Thank you for understanding. I look forward to what you might come up with next. This is a very powerful feature that could greatly ease plenty of stuff.