Page 1 of 2

Multiple extends

PostPosted: Tue Dec 25, 2018 10:49 pm
by Rip and Tear
One of the limitations of ZScript's `extend` functionality is that it can only extend classes defined in the same mod. Unfortunately, this makes it significantly less useful when working with classes that share a superclass in gzdoom.pk3. Often, you will need to either include the same code in multiple places, or replicate code that already exists in gzdoom.pk3.

I believe this feature suggestion would help to somewhat alleviate that. Class extensions should be able to be used on multiple classes at once, separated with commas. This would just be a bit of syntactic sugar added to the compiler.

Code: Select allExpand view
extend class foo, bar {
    //...
}

would be exactly equivalent to

Code: Select allExpand view
extend class foo {
    //...
}
extend class bar {
    //...
}

This would help to reduce code duplication when inheriting from actor classes in gzdoom.pk3. For example, if you were to define custom classes inheriting from Hexen's three playable classes, you could then add behavior to all three of them without having to copy/paste code.

Re: Multiple extends

PostPosted: Wed Dec 26, 2018 1:06 am
by Graf Zahl
I don't think this is a good idea. It would give modders a wrong impression of having behavior in different classes that can be accessed through a common interface. Even if this was implemented you'd still have two unrelated classes that may have copies of the same implementation but absolutely no means to access this in a common fashion.

Besides, since it still would be limited to classes in the same translation unit, it'd be better to code the common behavior properly into a base class where it is properly shared.
In short, all it'd do is allow for dirty implementations.

Re: Multiple extends

PostPosted: Wed Dec 26, 2018 9:17 am
by Rip and Tear
Graf Zahl wrote:Besides, since it still would be limited to classes in the same translation unit, it'd be better to code the common behavior properly into a base class where it is properly shared.

Though this would still require you to replicate code that already exists in gzdoom.pk3. Sticking with the Hexen class example, you would need to make make a class which inherits from PlayerPawn to include common behavior, then copy/paste the entire definitions for all three classes and inherit from the custom class.

The only other option I've come across is to make a helper class with static methods and pass `self` as the first argument. Which works, but is also very clunky.

Re: Multiple extends

PostPosted: Wed Dec 26, 2018 9:39 am
by Graf Zahl
Can you please lay out your use case? I somehow have a feeling that you are approaching this from the wrong angle conceptually.

Re: Multiple extends

PostPosted: Wed Dec 26, 2018 5:41 pm
by Rip and Tear
Graf Zahl wrote:Can you please lay out your use case? I somehow have a feeling that you are approaching this from the wrong angle conceptually.

Say someone wrote improved versions of the monster 'AI' functions (such as A_Chase) and I wanted to implement it into my Doom mod. Ideally, the per-actor implementation would just be

Code: Select allExpand view
class NewZombieMan : ZombieMan replaces ZombieMan {
    States {
    See:
        POSS AABBCCDD 4 A_EnhancedChase();
        Loop;
    }
}

In actuality, I would need to either copy and paste the functions into each actor definition; include the actor functions in a base monster class to inherit from, thereby losing the ability to inherit from the monster classes already defined in gzdoom.pk3; or use the clunky helper class method I described.

Perhaps this diagram will help to explain the problem.

Diagram.png

Re: Multiple extends

PostPosted: Wed Dec 26, 2018 7:48 pm
by Caligari87
One potential workaround is to use a static function in a separate thinker/class as a proxy. Matt does this in Hideous Destructor, for example.
Code: Select allExpand view
class NewZombieMan : ZombieMan replaces ZombieMan {
    States {
    See:
        POSS AABBCCDD 4 MyFunctions.EnhancedChase(self);
        Loop;
    }
}

class MyFunctions {
    static void EnhancedChase(actor caller) {
        //do fancy movement
        caller.vel.x += 15;
    }
}

8-)

Re: Multiple extends

PostPosted: Thu Dec 27, 2018 1:36 am
by Graf Zahl
To get here it is first necessary to scriptify all actor subclasses. Unfortunately there's still two major ones which aren't scriptable yet, namely PlayerPawn and DynamicLight, the former because it still depends on too much native functionality and the latter one because it is normally the one with the highest cumulative thinker overhead of all classes in a loaded map. To scriptify this one I first have to uncouple the light rendering from the backing actor and thanks to people hacking its properties live (since the args are modifiable even from ACS) this is not going to be easy without breaking stuff.

Re: Multiple extends

PostPosted: Thu Jan 03, 2019 9:18 am
by Rip and Tear
Caligari87 wrote:One potential workaround is to use a static function in a separate thinker/class as a proxy. Matt does this in Hideous Destructor, for example.
Code: Select allExpand view
class NewZombieMan : ZombieMan replaces ZombieMan {
    States {
    See:
        POSS AABBCCDD 4 MyFunctions.EnhancedChase(self);
        Loop;
    }
}

class MyFunctions {
    static void EnhancedChase(actor caller) {
        //do fancy movement
        caller.vel.x += 15;
    }
}

8-)

I'm aware of this method and it's definitely the best one available at the time. But having to prefix with `caller` so often leads to rather clunky syntax.

Re: Multiple extends

PostPosted: Thu Jan 03, 2019 9:40 am
by Graf Zahl
I'm working on it. I'm almost done exporting PlayerPawn which is the last remaining blocker for allowing user-side extends of class Actor.

Re: Multiple extends

PostPosted: Thu Jan 03, 2019 9:54 am
by Caligari87
excited incoherent screaming

Graf you're the best. This'll be a game-changer.

8-)

Re: Multiple extends

PostPosted: Thu Jan 03, 2019 10:18 am
by Major Cooke
How will this work when done?

Re: Multiple extends

PostPosted: Thu Jan 03, 2019 11:03 am
by Graf Zahl
It means that 'extends' blocks to all actor classes can be done in user code. The reason why this wasn't possible yet is that all the native children would have broken if a base class was extended.

For non-actors this isn't really important because there's little to be gained by extending them.

Re: Multiple extends

PostPosted: Thu Jan 03, 2019 11:30 am
by Rip and Tear
heavy breathing

Re: Multiple extends

PostPosted: Thu Jan 03, 2019 11:43 am
by Major Cooke
This sounds like it's eventually paving the way towards being able to inject/override states without needing to replace the actor. Removing the need to replace one actor with another just so it doesn't have to be given some little tweaks would be a godsend.

For example, if an addon modifies a state, it could TRULY replace it -- possibly even affecting the goto -- via some definition like:
Code: Select allExpand view
extends class Zombieman
{
States.MyAddonIdentifierHere
{
Spawn:


...which would be in the addon code, replacing the spawn state without replacing the actor. But the actor themselves could still use something like Goto <ClassName>::Spawn or something to make it use the original.

But then again I am effectively deluding myself into thinking its even possible. Feel free to correct me if I'm not deluding though!

Re: Multiple extends

PostPosted: Thu Jan 03, 2019 12:04 pm
by Graf Zahl
State label overrides won't be possible. That'd be too volatile.