ID:2019252
 
I've been trying to come up with scenarios where goto is useful in DM's syntax. I've only found a single pattern where it is actually useful and can't be better managed by more robust patterns (provided specific constraints).

For those of you that have found a good use for goto, would you be so kind to share your thoughts on the subject, why you use it in this case, and how your code using goto is patterned?
Found a second use for goto:

for ...
for ...
if(break_condition) goto LBL_break
LBL_break


goto is a good way to break out of a nested loop.

The real problem with goto is the lack of scope limitations. If you are careless with your use of goto, you can wind up jumping over scope cleanup or possibly even a declaration.
In response to Ter13
Breaking out of a nested loop with "break":
outer_loop
for ...
for ...
if(break_condition) break outer_loop
I feel like goto exists solely to make other processes seem more operational. Then again, there is the scenario where you have options to "go back" in a process, but really you can't utilize it unless you're using BYOND's default input proc.

mob/verb/Make_Character()
_name
var/name = input("Name") as text
_age
var/age = input("Age") as num
_icon
var/icon = input("Icon") as icon

switch(input("Finished?") in list("Yes","No"))
if("No")
switch(input("Return to which step?") in list("Name","Age","Icon"))
if("Name")goto _name
if("Age")goto _age
if("Icon")goto _icon
I thought goto was useless. Have never used that shit
In response to Konlet
Konlet wrote:
I feel like goto exists solely to make other processes seem more operational. Then again, there is the scenario where you have options to "go back" in a process, but really you can't utilize it unless you're using BYOND's default input proc.

mob/verb/Make_Character()
> _name
> var/name = input("Name") as text
> _age
> var/age = input("Age") as num
> _icon
> var/icon = input("Icon") as icon
>
> switch(input("Finished?") in list("Yes","No"))
> if("No")
> switch(input("Return to which step?") in list("Name","Age","Icon"))
> if("Name")goto _name
> if("Age")goto _age
> if("Icon")goto _icon


The issue with this is that it forces you to perform all the options underneath it as well.
You shouldn't be using input anyways, though. I was just giving an example.
goto is useful for creating a state machine without proc call overhead and stack management issue. It also ensures that the prototype tree stays clean of stuff that would otherwise force you to pass around list handles to act as temporary memory, or to use a datum to manage the flow of the state machine.

Konlet wrote:
but really you can't utilize it unless you're using BYOND's default input proc.

input()-based control flows are a very good use of goto in some cases, but really input() is a moot point because any function that is blocking and unreliable would show the exact same merits as Konlet's example.

As for "unless you are using BYOND's default input proc."

Completely not true.



The above gif was a test of a dynamic conversation engine. The basic code structure looks something like this behind the scenes:

"test"=list("label:confirm","cmd=resize;box=message;pos=0,0;size=256x80;","cmd=message;text=This is a test<BR>This is a test<BR>This is a test<BR>This is a test;last=0","cmd=resize;box=message;size=176x80","cmd=resize;box=choice;pos=176,0;size=80x80","cmd=choice&text=Do+you+want+to+hear+that+again%3f&choices=yes%3dconfirm%26no%3dcancel","label:cancel","cmd=resize;box=message;pos=0,0;size=256x80;")


^That's the deobfuscated control code that I'm using dumped from my database. The obfuscated variation of this is much less readable.

In DM, this conversation structure would actually look like this:

LBL_confirm
client.resizeHUDBox("message",0,0,256,80)
client.HUDmessage("This is a test<BR>",0)
client.resizeHUDBox("message",0,0,176,80)
client.resizeHUDBox("choice",176,0,80,80)
switch(client.HUDchoice("Do you want to hear that again?",list("yes","no"))
if("yes")
goto LBL_confirm
if("no")
goto LBL_cancel
LBL_cancel
client.resizeHUDBox("message",0,0,256,80)


Of course, the uncompiled code actually looks like this (if translated into DM vernacular):

do
client.resizeHUDBox("message",0,0,256,80)
client.HUDmessage("This is a test<BR>",0)
client.resizeHUDBox("message",0,0,176,80)
client.resizeHUDBox("choice",176,0,80,80)
while(client.HUDchoice("Do you want to hear that again?",list("yes","no"))=="yes")
client.resizeHUDBox("message",0,0,256,80)


It's the same pattern, only the micro programming language I wrote for handling conversations doesn't have if(), switch(), while(), or for() patterns when it's compiled. The uncompiled code has those patterns, but they compile down to label jumps.

TL;DR: this pattern is not exclusive to input() any blocking function that is unreliable can use this pattern.




In case anybody's curious about how this works under the hood...

This is what the deobfuscated, compiled language's interpreter objects look like without the larger portions. The portions I'm not including in this post are the portions concerned with memory management and condition testing:

conversation
var
mob/player
mob/npc/npc
list/conversation
position = 0
proc
Start()
var/list/l, cmd, len = conversation.len
while(++position<=len)
l = params2list(conversation[position])
cmd = l["cmd"]
if(cmd)
cmd = "CMD_[l["cmd"]]"
if(hascall(src,cmd))
l -= "cmd"
if(l.len)
call(src,cmd)(arglist(l))
else
call(src,cmd)()
if(player.client&&player.client.message_box.showing)
player.client.message_box.Hide()

CMD_resize(box,pos,size)
var/client/client = player.client
if(client)
var/interface/menubox/tbox
switch(box)
if("message")
tbox = client.message_box
if("choice")
tbox = client.choice_box
if(tbox)
var/x=tbox.x,y=tbox.y,w=tbox.w,h=tbox.h
if(pos)
x = text2num(pos)
y = text2num(copytext(pos,findtext(pos,",")+1))
if(size)
w = text2num(size)
h = text2num(copytext(size,findtext(size,"x")+1))
tbox.Resize(x,y,w,h)

CMD_message(text,last=1,position,size)
var/client/client = player.client
if(client)
if(istext(last)) last = text2num(last)
client.ChatMessage(text,last)
else
position = conversation.len

CMD_choice(text,list/choices)
var/client/client = player.client
if(client)
choices = params2list(choices)
var/selection = client.ChatChoice(text,choices)
if(selection)
selection = conversation.Find("label:[choices[selection]]")
if(selection) position = selection
else position = conversation.len
else position = conversation.len
else position = conversation.len

CMD_goto(label)
var/pos = conversation.Find("label:[label]")
if(pos) position = pos
else position = conversation.len

CMD_switch(id)
var/list/conv = conversations[id]
if(conv&&conv.len)
conversation = conv
position = 0
else
position = conversation.len

CMD_exit()
position = conversation.len

New(n,p,c)
player = p
npc = n
conversation = c
if(conversation&&conversation.len)
Start()


My programming language does make heavy use of "goto"-like structures under the hood, but they are used explicity for the purpose of creating if, while, switch, for, etc. patterns using a very small instruction set.
I never realized switch could be used like that to pause an operation like some of BYOND's defaults.
It can't be used to pause an operation. The condition inside of the switch statement is a blocking operation. The switch is irrelevant to the blocking/nonblocking, it's just a logic gate.

The "pause" you are talking about happens inside of the ChatMessage/ChatChoice procs:

client
proc
ChatMessage(msg,last=1)
message_box.setMessage(msg,last)
if(!message_box.showing)
message_box.Show()
while(!message_box.finished)
sleep(TICK_LAG)
if(last)
message_box.Hide()

ChatChoice(msg,list/choices)
message_box.setMessage(msg)
if(!message_box.showing)
message_box.Show()
choice_box.setMessage(choices)
if(!choice_box.showing)
choice_box.Show()
while(!message_box.finished)
sleep(TICK_LAG)
if(choice_box.selection)
if(choice_box.selection==-1) . = choice_box.cancel_option
else . = choice_box.choices[choice_box.selection]
choice_box.Hide()


Basically, I just keep a loop going inside of the choice/message functions that prevents the calling proc from returning until a certain condition is met --in which case, it's either when my message box object has been notified that it has been completed, which I have rigged up to the player's input functions. When the player presses the confirm keybind, it marks the messagebox as finished, allowing the ChatMessage() proc to return, thus allowing the parent function to continue.
start
world<<"Home"
world<<"Sweet"
goto start
In response to Rushnut
Rushnut wrote:
> start
> world<<"Home"
> world<<"Sweet"
> goto start
>


for()
world << "Home"
world << "Sweet"
If a proc needs to do cleanup work--this is much less of a concern in DM than in C++ of course--then goto may be a good choice. Basically goto replaces a return statement.

proc/DoSomething()
MakeMess()
...
if(condition1) goto wrapup
...
if(condition2) goto wrapup
...
wrapup:
CleanMess()

BYOND uses this pattern a great deal under the hood when temporary arrays are created. It's often the cleanest way to go. The compiler is capable of collapsing matching code blocks so it does the same thing, but the wrapup pattern allows all the cleanup code to be done in one place, consistently.
With well written code there's literally no reason for the average user to use goto with what there is in Byond.
proc/DoSomething()
MakeMess()
...
if(condition1) ...
else if(condition2) ...
CleanMess()


Is the same as using goto to wrapup. Either way if the first condition isn't met in either cases it's going to go to the end. If both conditions are checking the same variable, you might as well just use a switch if you're worried about any extra overhead. If you're going to have multiple versions of CleanMess you can just directly call return CleanMess(params) as well.


The only time I really see if chaining like that as something worthwhile is when you're actually returning a value. There's no point in not just using if if if in those.
no reason for the average user to use goto

I agree with this. The average user shouldn't need to use goto, but the abstracted pattern you mention induces more overhead.

I think there's a good case that goto is a much-maligned pattern that actually does have a lot of use in programming and is often overlooked as the best solution because it allows the user to make many mistakes the compiler would otherwise prevent using more robust patterns.

The trouble with robust patterns is that they come packaged with sacrifices that aren't obvious to someone who doesn't deeply understand the underlying structure of these patterns.
I once used goto to assign another random number to a variable because i was to lazy to copy and paste U.U.


It broke my program though :D.
In response to Ter13
My feelings exactly. A typical user shouldn't go anywhere near goto, because it can get you into trouble. But an advanced user may run across a situation where the only way out of using goto involves more overhead--or possibly an unacceptable sacrifice to speed, in code where performance is critical.

Only once a programmer has mastered regular loops and learned a lot more, do they really understand the costs and benefits of using a tool like goto. Also, its true best uses tend never to show up in simple cases, only complex ones.