Jump to content

Jumps vs Gosubs


ceedj

Recommended Posts

Didn't want to mess up a similar thread, so I'll post my shenanigans here.

 

Is there any difference between using a jump/jf vs a gosub? Beyond the obvious "subroutine" aspect, are there any speed differences, saving bytes, etc?

 

The reason I ask is this (from op9080's READ ME for the SA Loader)

 

 

Q: what are BareElse/BareElseIf/BareThen?

A: These are special forms that omit the auto-generated code and

  allow you more control.

 

Suppose you have code like this...

... some script code ...

script    << jump some_label;

script << Else;

... rest of script code ...

 

Normally, the Else generates a jump instruction to jump past the rest

of the else blocks to the end of the if statement.  However, in this

example, the auto-generated jump is unreachable since it's preceded

by a jump (might also be preceded by a return).  So you can use

the BareElse keyword instead to suppress the auto-generated jump.

 

... some script code ...

script    << jump some_label;

script << BareElse;

... rest of script code ...

 

BareElseIf is similar but replaces ElseIf.

 

BareThen is less useful - a Then generates an automatic

jump_if_false past the then block.  Suppose you have something like

 

script << If << condition << Then

script      << body_of_then

script << EndIf

script << jump someOtherPlace;

 

If the condition is false then 2 jumps will take place -

from the Then to past the EndIf, and from there to someOtherPlace.

You can short-circuit this double jump by using

 

script << If << condition << BareThen

script      << jump_if_false someOtherPlace;

script      << body_of_then

script << EndIf

script << jump someOtherPlace;

 

Note that it's never necessary to use the Bare form, so you needn't

worry about it if you don't want to.  It can just save a little

unreachable code space and some unnecessary double jumps.

 

 

Now, I do a fair amount of go-subbing in my code, and in fact use the BareElse/BareElseIf constructs an awful lot; this way I only have to do 1 routine for creating peds, cars, etc (this is actually much harder to pull off using C++, i.e Spookie's hook, because there is no gosub command in C++). So I'm curious to know if I'm saving more code space by using a gosub and return vs regular jumps/jf's.

 

As a second question, would it be possible to do "player defined" checks using gosubs? Say for example I have this:

 

 

theScript >> MyCode;theScript << wait << 0;theScript << actor_follow_actor << AnPed << PLAYER_ACTOR;theScript << jump << FreezeClock;

 

 

So could I slug a gosub in there after the wait to go to a routine that basically looks like this?

 

theScript >> PlayerDefined;theScript << If << is_player_defined << Then;theScript << _return;theScript << Else;theScript << jump << PlayerDefined;theScript << EndIf;

 

 

So this way if my player needs to be defined, I can just direct the code to gosub to that first. Or am I missing something? And as another "techie" question, what causes the player to become "undefined"? Is it because of the create_actor_from_player opcode used at the beginning of most SCM code? Just something I've always wondered.

 

We don't have a forum for discussing "coding" outside of answering questions/giving help. So if someone knows a better place for it, feel free to move it. Thanks! smile.gif

 

 

Link to comment
Share on other sites

Farming out the is player defined check isn't practical. The player is only ever undefined if you are wasted or busted (at least within normalcy). So you might have an is player defined check in the beginning of your code to get a script started. But later, a failed is player defined check might leave you wanting to dismiss a car, etc. Those different outcomes would mean that later, you'd have to return a flag. Which means that immediately after the gosub, you'd have an if flag equals check, which is just as heavy as is player defined. Only now, you'll need the defining code and other minor extras that make it doubly impractical.

 

I used to use gosubs just to save space when running duplicate code. Off the top of my head, the preparatory steps to a text draw is a good example. You might want to run the same few lines, so rather than having them repeatedly, you could have one block and gosub to it. In SCM, a gosub costs two bytes for the one return, plus seven bytes per call. Sometimes it's close in determining whether or not you gain space at all.

 

In terms of resources, I would imagine a gosub is ever so slightly slower than a jump. The execution of one isn't any heavier though as the addressing is stored in negative locals, which are built into every thread already.

 

Now, I use subroutines to act as code modules since I tend to mostly work on larger projects anymore.Thirst For Blood is a fantastic example of this. It REALLY helped in the refinement stage as I was able to turn entire features off by simply commenting out a gosub line. Windshield might be an even better example. Under most circumstances, it would run each feature. But sometimes (the schools I think), I didn't want to run ALL the features. So I had X subroutines at the helm of normal circumstances with <X subroutines during those special circumstances.

Link to comment
Share on other sites

Which is better? Well a gosub will save resources same code same variables no excess waste but as for the execution time surely a gosub will take longer. At any rate its miles better than using double jumps if you need to repeat the process a few times. But how many times must a piece of code be called before it becomes more efficient using a gosub instead of normal jumps/jf?

Link to comment
Share on other sites

 

But how many times must a piece of code be called before it becomes more efficient using a gosub instead of normal jumps/jf?

If the code is greater than 16 bytes heavy, then the answer is twice. If it's 12-16 bytes heavy, three times. And so on. The equation for determining this is (7X+2)/(X-1) rounded up, I think. X being the number of times you would gosub to the code block overall.

Link to comment
Share on other sites

Ok, consider this:

 

 

theScript >> AnimCheckA;theScript << wait << 0;theScript	<< If << egi << ClockCh << 1 << Then; // Is clock frozen? theScript	<< set_time << TH << TM;theScript	<< force_weather << WeatherSelect;theScript	<< EndIf;theScript << If << eli << AnimPed << 1 << And << eli << PedCheck1 << 1 << Then;theScript << setgg << AnPed << Ped1;theScript << setli << AnimPed << 0;theScript << gosub << CommonPath;theScript << jump << FreezeClock;theScript << BareElse;theScript << jump << AnimCheck2;theScript << EndIf;theScript >> AnimCheck2;theScript << wait << 0;theScript << If << eli << AnimPed << 2 << And << eli << PedCheck2 << 1 << Then;theScript << setgg << AnPed << Ped2;theScript << setli << AnimPed << 0;theScript << gosub << CommonPath;theScript << jump << FreezeClock;theScript << BareElse;theScript << jump << AnimCheck3;theScript << EndIf;(SNIP)theScript >> CommonPath;theScript << actor_swim_ability << AnPed << 0;theScript << wait << 0;theScript << If << eli << AnimKey << 101 << Then;theScript << AT_move_mouth << AnPed << 999999;theScript << setli << AnimKey << 0;theScript << _return;theScript << BareElseIf << eli << AnimKey << 102 << Then;theScript << AT_stop_mouth << AnPed;theScript << setli << AnimKey << 0;theScript << _return;theScript << BareElseIf << eli << AnimKey << 103 << Then;theScript << AT_animation << AnPed << "IDLE_CHAT" << "PED" << 4.0 << 0 << 0 << 0 << 0 << -1;theScript << setli << AnimKey << 0;theScript << _return;ect...

 

 

Now after the second snip, there are about 25 or so more blocks, similar to the last three above. In the top example, you can see I'm doing jumps to progress the initial keypress (if it's a MODIFIER, i.e., LF SHIFT, LF CTRL, LF ALT, etc). Then the gosub is to send it to the CommonPath, which takes my AnPed variable and applies my animations to that ped (or player).

 

So I'm wondering if this is the best way to go. On one hand, it's really easy to update if I need to add/remove sections, but maybe that would be just as easy with jumps. I guess I'm just REALLY concerned with performance, because up until last week, I'd never really USED my SA mod (being in VC and Liberty for the past two years), so I was really unaware how slow it was. Of course, I've since removed the "bank" switcher and went with an external file for users to add whatever animations they want, and it's MUCH faster now, but any extra speed I can eek out is a good thing.

 

@Dem, RE: Player defined: Makes sense. The reason I ask about farming it out is because there are quite a few sections of code that get the coords from the player, follow the player, etc, so I'm trying to knot up any potential crashes. The thing is, aside from the issue I was having with overlapping globals (fixed, thanks space and seamann!), I've never been able to crash it, so it's hard to debug any problems my users may be having. I'm thinking MAYBE it's a lack of player defined checks, so i guess it can't hurt to have them, but again, it's difficult for me to verify.

 

The other thing is that the hook I use is "transparent" (for lack of a better term), so the game doesn't even really know it's there. It relies on a standard, un-modded main.scm; maybe that is taking care of player defined checks for me, since I never mod my main.scm.

 

Thanks for the discussion on this; fascinating stuff.

Link to comment
Share on other sites

 

@Dem, RE: Player defined: Makes sense. The reason I ask about farming it out is because there are quite a few sections of code that get the coords from the player, follow the player, etc, so I'm trying to knot up any potential crashes.

You don't have to use an if player defined check every single reference to the player. wait -> is player defined -> if true, then you are free to talk about the player to your heart's desire until you execute another wait. Because until that thread is given another wait, the engine will not have yet processed that the player is in fact not defined. As far as the game handling is player defined checks for you, I'm not at all familiar with how the hook works, so I wouldn't know. If it's allowing you to inject code within an existing thread, I could see that. Again, if your injection has its own wait, you would have to make another check. If it just allows you to execute your own threads without having to modify the physical SCM file, then no, it's still your responsibility. If you're unable to crash it, it is likely because is player defined is a formality we observe. I don't know if there's ever been an actual case of the game crashing from a lack of the same. I could be wrong.

 

Keep in mind too that if you have many if player defined checks that all have the same result (truncating the thread, removing references to an actor, THEN truncating the thread, etc), you could always incorporate a mock gosub. When I went through SA's original missions to compress them, I found blocks of code that were called upon from several points, but might've had slightly different paths after the fact. So what I would do here is use a variable to store an absolute address. Then I'd jump to the code block, at the end of which was a jump $variable. Keep in mind that using jump $variable as well as the line to set that variable, the farming out must be that much bigger a code block in order to justify such a procedure. This is pretty advanced though, so nobody should be itching to use it unless it's actually useful.

 

As for your method of approach, I'm not sure I follow the question. I THINK I can follow that code well enough. If I can, then it looks like the way you've done it allows you to use return (two bytes) at the end of every possibility, of which you say there's some 28 of them. Since returning always takes you back to just the jump command (seven bytes) in your code, you are saving five bytes. This is only useful if you have the single (or just plain lower amount) gosub as you do, because a gosub and jump command weigh the same. I consider this to be a more advanced use of gosub since the benefits aren't readily clear.

 

While I'm posting, I might as well report that I made a slight error in my equation in my last post. Since the resultant number is what the weight of the code block must be GREATER THAN to be useful, it was wrong of me to say that the resultant number should be rounded up.

Edited by Demarest
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • 1 User Currently Viewing
    0 members, 0 Anonymous, 1 Guest

×
×
  • Create New...

Important Information

By using GTAForums.com, you agree to our Terms of Use and Privacy Policy.