• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[JASS] Reverse/Return-Hook

Status
Not open for further replies.
Level 39
Joined
Feb 27, 2007
Messages
5,010
Has anyone else run into a situation in which you needed a way to hook into a function to get its output rather than the inputs given to it? Like the hook keyword but in reverse. When hooks are used they are called before the function itself and do not provide you with a way to get its return arguments. By way of example:
JASS:
function FuncA takes nothing returns nothing
  call BJDebugMsg("A")
endfunction

function FuncB takes nothing returns nothing
  call BJDebugMsg("B")
endfunction

hook FuncA FuncB //B is hooked when A is called
//...
call FuncA() //The output is B,A not A,B
This behavior is fine, (somewhat) expected, and useful in many situations. But what about if you want to hook something like CreateUnit so that you can do some actions with the unit after it's created? With the way hooks work now, this is impossible because you can't get the return value of the function. I propose something like:
JASS:
function FuncA nothing returns integer
  call BJDebugMsg("Random math:")
  return GetRandomInt(1,3)
endfunction

function FuncB takes integer RET_VALUE returns nothing
  call BJDebugMsg(I2S(RET_VALUE)+"times 5 is "+I2S(RET_VALUE * 5))
endfunction

hookreturn FuncA FuncB //A's output is hooked into B after it's
//...
call FuncA() //the output is "Random math: _ times 5 is _"
Why would this be useful because you should theoretically always have access to the return value of a function you called, right? Besides something like the CreateUnit scenario I mentioned before where you might want to set mapwide functions to run whenever a unit is created (from any source in your map) the scenario that got me thinking about this is trying to hook into CreateTrigger to do something with it whenever it's called. Since the function has no arguments hooking immediately seemed pointless since I can't actually access the trigger object that was created by the function! Sure I could write a CreateTriggerEx that called CreateTrigger itself and did whatever I needed to do, but implementing that in a map would require changing all CreateTrigger calls to use the Ex version--could be a lengthy process to find/replace all your code (I guess not with editing the war3map.j file though).


Thoughts?
 

Wrda

Spell Reviewer
Level 26
Joined
Nov 18, 2012
Messages
1,887
I don't know. I think it's a legit idea, but I see little gain with it. I'd stick with CreateTriggerEx kind of stuff. Obviously if you're doing this since the start of your map, it is effortless, but if you're trying to do this in middle of it it is surely tedious.
PS: I never really used hooks, just my thoughts.
 
I don't use vJASS hooks due to the fact that they evaluate triggers. So I came up with my own approach. It's still limited by a few things:

1. It only works in scopes and libraries.
2. It's a bit convoluted.
3. Supporting multiple hooks would be a pain.

Code:
library FuncHooks requires AttachedSFX

//! runtextmacro FuncHooks("Orgl", "SetUnitPosition", "unit u, real x, real y", "u,x,y")
//! runtextmacro FuncHooks("Orgl", "SetUnitFacing", "unit u, real a", "u,a")
//! runtextmacro FuncHooks("Orgl", "SetUnitFacingTimed", "unit u, real a, real t", "u,a,t")
//! runtextmacro FuncHooks("Orgl", "SetUnitTimeScale", "unit u, real scale", "u,scale")
//! runtextmacro FuncHooks("Orgl", "SetUnitScale", "unit u, real x, real y, real z", "u,x,y,z")
//! runtextmacro FuncHooks("Orgl", "SetUnitFlyHeight", "unit u, real h, real r", "u,h,r")
//! runtextmacro FuncHooks("Orgl", "AddUnitAnimationProperties", "unit u, string p, boolean a", "u,p,a")
//! runtextmacro FuncHooks("Orgl", "SetUnitColor", "unit u, playercolor c", "u,c")
//! runtextmacro FuncHooks("Orgl", "SetUnitVertexColor", "unit u, integer r, integer g, integer b, integer a", "u,r,g,b,a")

//! runtextmacro optional FuncHooks_Definitions()

//! runtextmacro FuncHooks("Hkd", "SetUnitPosition", "unit u, real x, real y", "u,x,y")
//! runtextmacro FuncHooks("Hkd", "SetUnitFacing", "unit u, real a", "u,a")
//! runtextmacro FuncHooks("Hkd", "SetUnitFacingTimed", "unit u, real a, real t", "u,a,t")
//! runtextmacro FuncHooks("Hkd", "SetUnitTimeScale", "unit u, real scale", "u,scale")
//! runtextmacro FuncHooks("Hkd", "SetUnitScale", "unit u, real x, real y, real z", "u,x,y,z")
//! runtextmacro FuncHooks("Hkd", "SetUnitFlyHeight", "unit u, real h, real r", "u,h,r")
//! runtextmacro FuncHooks("Hkd", "AddUnitAnimationProperties", "unit u, string p, boolean a", "u,p,a")
//! runtextmacro FuncHooks("Hkd", "SetUnitColor", "unit u, playercolor c", "u,c")
//! runtextmacro FuncHooks("Hkd", "SetUnitVertexColor", "unit u, integer r, integer g, integer b, integer a", "u,r,g,b,a")

endlibrary

//! textmacro FuncHooks takes prefix, name, typedArgs, args
function $prefix$$name$ takes $typedArgs$ returns nothing
call $name$($args$)
endfunction
//! endtextmacro

//! textmacro DefineHooks
private function SetUnitPosition takes unit u, real x, real y returns nothing
call HkdSetUnitPosition(u, x, y)
endfunction

private function SetUnitFacing takes unit u, real a returns nothing
call HkdSetUnitFacing(u, a)
endfunction

private function SetUnitFacingTimed takes unit u, real a, real t returns nothing
call HkdSetUnitFacingTimed(u, a, t)
endfunction

private function SetUnitTimeScale takes unit u, real scale returns nothing
call HkdSetUnitTimeScale(u,scale)
endfunction

private function SetUnitScale takes unit u, real x, real y, real z returns nothing
call HkdSetUnitScale(u,x,y,z)
endfunction

private function SetUnitFlyHeight takes unit u, real h, real r returns nothing
call HkdSetUnitFlyHeight(u,h,r)
endfunction

private function AddUnitAnimationProperties takes unit u, string p, boolean a returns nothing
call HkdAddUnitAnimationProperties(u,p,a)
endfunction

private function SetUnitColor takes unit u, playercolor c returns nothing
call HkdSetUnitColor(u,c)
endfunction

private function SetUnitVertexColor takes unit u, integer r, integer g, integer b, integer a returns nothing
call HkdSetUnitVertexColor(u,r,g,b,a)
endfunction

//! endtextmacro
// Missing Facing hook\

This is where I define the hooks (this is executed as the FuncHooks_Definitions() textmacro in the code above):
Code:
/* Functions that are not defined here will not have any hooks attached. To see which functions can be
hooked to, check the FuncHooks library.*/
//! textmacro FuncHooks_Definitions

private function SetUnitPosition takes unit u, real x, real y returns nothing
call OrglSetUnitPosition(u, x, y)

if UnitHasAttachedEffect(u) then
call AttachedSFX_onSetPosition(u, x, y)
endif
endfunction

private function SetUnitFacing takes unit u, real angle returns nothing
call OrglSetUnitFacing(u, angle)

if UnitHasAttachedEffect(u) then
call AttachedSFX_onSetFacing(u, angle)
endif
endfunction

private function SetUnitFacingTimed takes unit u, real angle, real time returns nothing
call OrglSetUnitFacingTimed(u, angle, time)

if UnitHasAttachedEffect(u) then
call AttachedSFX_onSetFacingTimed(u, angle, time)
endif
endfunction

private function SetUnitTimeScale takes unit u, real scale returns nothing
call OrglSetUnitTimeScale(u,scale)

if UnitHasAttachedEffect(u) then
call AttachedSFX_onSetTimeScale(u, scale)
endif
endfunction

private function SetUnitScale takes unit u, real x, real y, real z returns nothing
call OrglSetUnitScale(u,x,y,z)

// Here
call AttachedSFX_onSetScale(u, x, y, z)
endfunction

private function SetUnitFlyHeight takes unit u, real h, real r returns nothing
call OrglSetUnitFlyHeight(u,h,r)

// if UnitHasAttachedEffect(u) then
call AttachedSFX_onSetHeight(u, h, r)
// endif
endfunction

private function AddUnitAnimationProperties takes unit u, string p, boolean a returns nothing
call OrglAddUnitAnimationProperties(u,p,a)

if UnitHasAttachedEffect(u) then
call AttachedSFX_onAddAnimationProperties(u, p, a)
endif
endfunction

private function SetUnitColor takes unit u, playercolor c returns nothing
call OrglSetUnitColor(u,c)

if UnitHasAttachedEffect(u) then
call AttachedSFX_onSetColor(u, c)
endif
endfunction

private function SetUnitVertexColor takes unit u, integer r, integer g, integer b, integer a returns nothing
if UnitHasAttachedEffect(u) then
call AttachedSFX_onSetVertexColor(u, r, g, b, a)
else
call OrglSetUnitVertexColor(u,r,g,b,a)
endif
endfunction


//! endtextmacro


So now, whenever I need to hook these functions in any library or scope, I simple include the text macro:
//! runtextmacro optional DefineHooks()

This way my code stays pretty decoupled, since I can remove the hook library and it won't generate any compile errors.

Hopefully this makes sense, I was going to try to explain it better, but it's late and I'm tried. But basically, I'm replacing the natives by taking advtanges of the fact that vJASS allows us to overwrite any functions outside of the library or scope within that library or scope by declaring a function with the same name, but with private or public access.
 
Level 6
Joined
Mar 7, 2011
Messages
124
so the syntactic sugar would sorta like supply a specific eventable api, where a lot of the specifics are determined by the "reverse-hooked" function's signature? i get a lot of eventable-from-a-different-angle vibes from this idea. like a function/method that implements the eventable api at the instance or static level. the creator can broadcast an event with data, but the things that listen to that event effectively are hooking (one of) its results. given that jass is entirely interpreted, the difference between "compiling" this "reverse-hook" behavior VS defining it in memory on load is often not relevant beyond the immediate implementation

having this feature would mean fairly custom events on warcraft 3 natives (and any other custom function) as such sweet syntactic sugar that there's a little less of a learning curve to dealing with it (but more limitations instead). You'd potentially never even need to worry about a more flexible event system at all, while getting a significant upgrade to how your implementation can extend/maintain its dependencies. On that note, i feel like when you get to the point that you want to depend on a function's output, ie events, you often don't want to lock into generalizations like output type or number of outputs per function. yeah, it's nice when every function has a single clear output and type, but there's going to be a lot of times where you'll want to depend on two parts of a function's "output", or not the obvious candidate. this in turn leads to having your own alternate implementation for custom events, or more granular functions, which can be their own performance liability

i might like this idea even more if there was better vanilla support for defining/depending on custom events. that way you could maintain one or the other implementation, probably the sugar, to be consistent with the other. do you have any thoughts on that?
 
Status
Not open for further replies.
Top