Semi-tutorial - Modular scripting and looping in geckscript

Post » Fri Aug 13, 2010 8:14 am

The code examples throughout this are in most cases written for clarity, not optimization.

-Modular Scripting

If you are scripting a process which needs to use different portions of itself for different functions, you can effect this through what I have been calling to myself fall-through logic. This sort of structure would be used to effect what could, in other environments, be done using subprograms or "goto". It uses a variable state to trigger the script to know that it needs to run a certain block of code (or, exclude it).

An example of that sort of structure is like this.

scn ModularScriptshort PostProcessingbegin gamemode	MyREF.getdistance MyEnemyREF	;do stuff	if MyREF.getitemcount Ammo10MM == 0		set PostProcessing to 1	endif	if PostProcessing == 0		;do stuff that requires 10mm ammo		;do more stuff that requires 10mm ammo	endif	;do some stuff that always gets done anyway	if PostProcessing == 1		set PostProcessing to 0		;do special stuff for one-time situation of Ammo10MM == 0.	endifend


If you wanted to modify this for greater efficiency as it runs, while keeping the identical integrity to the process, you would change it like this.


	MyREF.getdistance MyEnemyREF	;do stuff	if MyREF.getitemcount Ammo10MM	else		set PostProcessing to 1	endif	if PostProcessing	else		;do stuff that requires 10mm ammo		;do more stuff that requires 10mm ammo	endif	;do some stuff that always gets done anyway	if PostProcessing		set PostProcessing to 0		;do special stuff for one-time situation of Ammo10MM == 0.	endif


Note that the above code may not work the way you need it to if the "PostProcessing" variable is used in any capacity other than being zero or one. The above optimization treats all nonzero values of the PostProcessing variable identically.

Because of your processes' requirements, you oftentimes cannot collapse the code into a simple single block of code. The integrity of the code ordering is sometimes critical, as later parts of the process can depend upon parts which ran earlier. A possible oversimplification of the above code is shown below - - this code would only work if none of the operations in the original process were interdependent.


	MyREF.getdistance MyEnemyREF	;do stuff	;do some stuff that always gets done anyway	if MyREF.getitemcount Ammo10MM		;do stuff that requires 10mm ammo		;do more stuff that requires 10mm ammo	else		;do special stuff for one-time situation of Ammo10MM == 0.	endif


The above is not fall-through logic any more, it's just an if/then/else block. It is only a valid optimization if it does not break the process (which it easily could). This version of the code could potentially output garbage.

A structure of allowing your process to fall-through and bypass sections of code based upon your variable state assignments can manage complicated processes with a lot of nesting. It allows you to create virtual subprogram calls and create process flow which could otherwise have been done using a goto function.

The 'return' command can save you space and processing time in this kind of coding. If you know a block of code has gone as far as you intend it to for this script iteration, you can put 'return' as the last line of the code section, and the processing will end, and in the next frame/iteration it will begin processing again from the top of the script.

;------------------

- Looping

Looping comes naturally to geckscript if your process can tolerate a loop-over-time. My experience with the other looping structure where a loop-like process must happen over one or a few frames is that you design your process to be distributed into multiple scripts which are running in pretty much the same frame. Geckscript does not have a native ability that I am aware of to do a true loop within a single script iteration, and if you absolutely need something like this, you do it by distributing your process.

Discussing looping-over-time first, and distributed processed loop second.

Looping over time can work with blocktypes that by its nature runs repeatedly, such as ScriptEffectUpdate, Gamemode and technically Menumode, and it also works in quest scripts. Quest scripts allow you to set the speed of your loop in realtime by changing the quest update speed (which is in seconds and fractions thereof). Other scripts will loop as quickly as once per frame, depending on how you build them. BTW with effect scripts, I've only got experience using actor effects for it, I happen to have not had a need to try it with an object effect.

An example of looping code resembles below. The process only begins when something causes the StartMe variable to be toggled to 1. This could happen either from a code block elsewhere in the script, or, a function outside of the script entirely.

scn LoopyScriptshort StartMeshort ControlMeshort LoopCountbegin gamemodeif StartMe == 1 && ControlMe == 0	set ControlMe to 1	set StartMe to 0	set LoopCount to 1endifif ControlMe == 1	set LoopCount to LoopCount + 1	;do stuff	if LoopCount > 10		set ControlMe to 0	endifendifend


Part of this code's process prevents the looping process from being interrupted by an outside source toggling that StartMe variable while it is still looping. The behavior on that as above is, if the script is looping already and a second call for a loop happens via the StartMe variable, it will wait until it's finished its current loop and then start one additional loop. During that additional loop, it will be prepared for some source to again toggle the StartMe variable to 1 which effectively requests another loop. You could change the code to handle that differently, of course, pehaps make it count the number of times that an interrupting-sort-of StartMe variable change was pushed through, and, chain loop based upon that value. It all depends on what you need.

The code above, if run in gamemode, loops 10 times, one frame per loop. You can slow this down to every other frame with this edit:

scn LoopyScriptshort StartMeshort ControlMeshort LoopCountshort Togglebegin gamemodeif StartMe == 1 && ControlMe == 0	set ControlMe to 1	set StartMe to 0	set LoopCount to 1endifif Toggle == 1	set Toggle to 0else	set Toggle to 1endifif ControlMe == 1 && Toggle == 1	set LoopCount to LoopCount + 1	;do stuff	if LoopCount > 10		set ControlMe to 0		set LoopCount to 0	endifendif


If you are using an effect script, you have the option of organizing your initialization-sorts of operations in a ScriptEffectStart block and putting the meat of your loop in ScriptEffectUpdate, but, it's not necessary, you can do it all within the ScriptEffectUpdate block if you want. My experience with this is with actor effects, but if you needed it in an object effect, you could try it.

In my experience with actor effects, there may be some caviats to note. #1, the ScriptEffectUpdate block seems that it might run twice in a single frame the very first time you call it, and then once per frame thereafter, so be prepared to keep an eye out for that, in particular if you're electing not to use a ScriptEffectStart block. #2, it seems best to assume that your scripteffectupdate block could run more times than you expected, including after you've issued the necessary 'dispel' script command. Build explicit controls that don't allow it to iterate more than your intended max.

An example if you wanted to use both blocks of an effect script in an actor effect, and presume that the application of the effect is meant as the beginning of your loop:

scn LoopyEffectScriptshort ControlMeshort LoopCountbegin scripteffectstart	set ControlMe to 1	set LoopCount to 1endifendbegin scripteffectupdate	if ControlMe == 1		set LoopCount to LoopCount + 1		;do stuff		if LoopCount > 10			set ControlMe to 0			dispel MyLoopyActorEffect		endif	else		dispel MyLoopyActorEffect ;maybe	endifend


You can also cause effect scripts to skip frames with a toggle as earlier.

Discussing distributed-process looping. My own need for this kind of processing is rare so I have limited experience with it. The below stuff should be an example of a way to do it but I don't recall ever personally having to do exactly this thing (no need). So, if someone tries this thing and finds that something doesn't work right, post something and if I'm around I might give what you are trying to do a shot. I've done similar, but not exactly this.

You would be using a control script of sorts to get the process started, and a control sort of variable which lives associated with just one quest, object, or is a global variable, and, it requires you to select the type of distributed script which best suits your process.

For example. You could have a quest or object script which looks like this. The distributed processing is intended to begin after something toggles that DoStuff variable to 1.

scn MyMainObjectScriptshort DoStuffshort ControlProcessbegin gamemode	if DoStuff == 1		MyOtherScriptedObject0REF.enable		MyOtherScriptedObject1REF.enable		MyOtherScriptedObject2REF.enable		MyOtherScriptedObject3REF.enable		MyOtherScriptedObject4REF.enable		MyOtherScriptedObject5REF.enable		set DoStuff to -1	endifend



And the script which is attached to all of the MyOtherScriptedObject items looks like this:

MyOtherScriptedObjectSCRIPTbegin gamemode	if MyMainObject.ControlProcess == 0		;do something		set MyMainObject.ControlProcess to MyMainObject.ControlProcess + 1	elseif MyMainObject.ControlProcess == 1		;do something		set MyMainObject.ControlProcess to MyMainObject.ControlProcess + 1	elseif MyMainObject.ControlProcess == 2		;do something		set MyMainObject.ControlProcess to MyMainObject.ControlProcess + 1	elseif MyMainObject.ControlProcess == 3		;do something		set MyMainObject.ControlProcess to MyMainObject.ControlProcess + 1	elseif MyMainObject.ControlProcess == 4		;do something		set MyMainObject.ControlProcess to MyMainObject.ControlProcess + 1	elseif MyMainObject.ControlProcess == 5		;do something		set MyMainObject.ControlProcess to -1	endifend


The above sort of arrangement should, for some processes, manage what would behave like a loop done single-frame, but as you can see, whether this is useful depends upon that processes' goals and needs. Other similar structures would be calling down several actor effects on various npcs/creatures at the same time, or starting multiple quests with scripts attached. The key to this sort of processing is that there is a unique sort of control variable which is not held locally by the distributed scripts which is acting as the loop control. Like I said I don't tend to roll this way but I think this method should be usable.
User avatar
Ladymorphine
 
Posts: 3441
Joined: Wed Nov 08, 2006 2:22 pm

Post » Fri Aug 13, 2010 10:23 pm

Thanks again for this Tarrant. :) I think that I could rebuild my scripts to work without FOSE now. Like many I'm addicted to looping inside a single frame with FOSE code, but come to think of it, there are very few things that Demand being done in one frame that I couldn't work around. Interesting food for thought..

Cheers!

Miax
User avatar
MatthewJontully
 
Posts: 3517
Joined: Thu Mar 08, 2007 9:33 am


Return to Fallout 3