Page 1 of 1

Intricacies of byond

Posted: Thu Jun 12, 2014 4:58 pm
by carnie
General thread for informing others about hidden-features, quirks, tricks etc with byond.

Try to provide as much info as possible about stuff, as to avoid misunderstandings.
Preferably avoid submitting anything which is in the DMreference, which you simply only just found out about, this is more for obscure inner workings and such, beyond that which the DMreference tells us.

I'll start with how the map is instanced and initialized at round start:
First to explain what I mean by instance, instanced and initialized, since I am self-taught and probably misusing these terms in some way.

instance = any 'thing' which is created. For example, /atom, /datum, /list, /image, /icon are all instances. i.e. and class which has been brought into existence.
instanced = the creation of an instance. e.g. calling new /some/typepath()
initialized = probably completely misusing this term. I'm using it to mean New() has been called for this instance.

Ok, so when the map is created, as far as I can tell, this roughly outlines the order of things. Some of this is no doubt inaccurate in some insignificant technicalities, but it's accurate enough to draw some helpful conclusions (which I will get to later):
1) all turfs are instanced according to the dmm
2) all areas are instanced and any turfs are added to their contents
3) area/New() called for all areas starting from bottom left travelling left-to-right
(//Note: I have yet to test whether -all- areas are instanced and -then- -all- their New are called, or if each area is instanced and initialized sequentially.)
4) turf/New() called for all turfs starting from the top right, travelling right-to-left
5) for each turf, starting at the bottom left, travelling left-to-right, we instance and initialize objects for that turf, then before moving on to the next turf, instance and initialize mobs.
(//Note: this may actually be instancing all atom/movable for the turf and inserting them into the contents list in this order (as they always appear in that order in the contents list), then simply calling New() for each thing in the turf's contents)

If you can improve this please do.

conclusions:
much code contained in spawn() within New() procs can be reworked to not need spawn() by using the information above.
for instance, take:

Code: Select all

/area/station/ai_monitored/New()
	..()
	spawn(4)
		// locate and store the motioncamera
		for(var/obj/machinery/camera/M in src)
			if(M.isMotion())
				motioncamera = M
				M.area_motion = src
Since areas are created before objects, we could simply move this to:

Code: Select all

/obj/machinery/camera/New()
	..()
	if(isMotion())
		var/area/station/ai_monitored/A = get_area(src)
		if(istype(A))
			A.motioncamera = src
			area_motion = A
eliminating the need for spawn (and a loop through a large contents list)

another example would be mimic crates collecting all objects in loc.contents and moving them to its contents during mimic/crate/New(). This was originally done with a spawn, but since we know that mobs are initialized after objects, we can just omit the spawn, since we do not need to collect objects from neighbouring turfs (which would not even be instanced yet), just our own turf.

Another case would be the smoothwall code. This is performed in turf/New() obj/structure/falsewall/New() and /obj/structure/falserwall/New(). It searches surrounding turfs for these objects/turfs to update their icon_states.
It can work without spawn because we know that all turfs will be instanced for any of those New() calls.
However, neighbouring objects -not- necessarily be instanced yet. So those will not be smoothwalled.
But if we think carefully about it, we know that objects are initialized from bottom left to top-right. So the smooth-wall code will smooth all of these objects with anything to the west, southwest, southeast or south. Therefore, everything will be smoothed properly since we are travelling in the opposite direction. No need to wait until the map is fully instanced.

Another more difficult case, is when a machine looks to 'link' itself with neighbours during New(). Since only objects to the south,west, southeast and southwest will be instanced by this point. However, by having both partners in the link attempting to find partners, rather than just one of the partners searching, we will always find any partners to the bottom left. Meaning that we no longer need a spawn() in their New().

Sorry if this is really hard to read, I'm not the best at explaining stuff. I hope it helps somebody.

Re: Intricacies of byond

Posted: Thu Jun 12, 2014 9:21 pm
by Remie Richards
PARENT_TYPE VAR:

Not sure whether it's known but:
You can change the parent_type var of anything.
This has the unique bonus of Object oriented programming in situations it'd be hard to do.

For example, you can have a /datum that is at it's most basic level, just /datum (it's /datum/atom isn't it?) But you can set it's parent_type var to /obj and then you can place that datum on the map, because it's a /datum/obj/datum technically. This is really dark magic tier coding and I have no idea if this is used anywhere in SS13, but it let's something have the flexibility of it's own typepath and any typepath supplied to parent_type.

However, I do believe it makes ..() calls check the parent_type var, and not the "real" typepath, which might not be wanted.

TYPE VAR:

Also if you wanted to check the type of an instance without allowing it's children through the check (like istype() does) you can literally just if(type == /atom/exampletypepath) and that will allow ONLY exact matches through. Sadly you can't change the type var for "safety" reasons, but if you could, you'd probably ruin the world.

Re: Intricacies of byond

Posted: Thu Jun 12, 2014 9:59 pm
by Malkevin
Don't start an atom name with a number, e.g.

Code: Select all

obj/ammo/44magnum
Dream maker will throw a fit and wont compile, and it will give you a completely useless error that gives no indicate that the issue is in the typepath.


Pickweight() also does not like nums, unless they're in strings, e.g.:
Bad:

Code: Select all

pickweight(10 = 1, 5 = 2)
Good:

Code: Select all

text2num(pickweight("10" = 1, "5" = 2))
Trying to do it the bad way WILL compile without errors but will cause a runtime: "array index out of bounds" when called.

Re: Intricacies of byond

Posted: Thu Jun 12, 2014 10:14 pm
by Remie Richards
Malkevin wrote:Don't start an atom name with a number, e.g.

Code: Select all

obj/ammo/44magnum
Dream maker will throw a fit and wont compile, and it will give you a completely useless error that gives no indicate that the issue is in the typepath.
The same goes for +- and a few other operators
the typepath is not the place for those!

Re: Intricacies of byond

Posted: Thu Jun 12, 2014 10:17 pm
by paprika
this is why the 1911 is the m1911 in the code

Re: Intricacies of byond

Posted: Thu Jun 12, 2014 11:16 pm
by carnie
Malkevin wrote:Pickweight() also does not like nums, unless they're in strings, e.g.:
Bad:

Code: Select all

pickweight(10 = 1, 5 = 2)
Good:

Code: Select all

text2num(pickweight("10" = 1, "5" = 2))
Trying to do it the bad way WILL compile without errors but will cause a runtime: "array index out of bounds" when called.
I have a class for weighted lists if anybody wants it. It's basically just two sync'd lists with a total_weight variable. It gets around this problem, and you don't have to loop through the entire list each time to get the total_weight for doing pickweight()

Re: Intricacies of byond

Posted: Sat Jun 21, 2014 2:45 pm
by Miauw
src is a var that can be set just like any other var. i guess you could use this to delete the parent object but keep a proc running when youre for example doing something like monkify (but it's still rather useless).



PREFIXING THINGS WITH SRC. IS ALMOST ALWAYS UNNEEDED SO DONT DO IT FOR FUCKS SAKE



there's a special thing you can do where you add

Code: Select all

set waitfor = 1
to a proc and it makes sleep act like spawn. It's undocumented though so dont use it.



There's a "to" operator in BYOND that generates a list of numbers containing all numbers between two given numbers
(e.g. 5 to 10 gives [5, 6, 7, 8, 9, 10] (and no im not sure wether 10 is included but im fairly sure about 5 (not entirely though)))

Re: Intricacies of byond

Posted: Sat Jun 21, 2014 5:58 pm
by MisterPerson
5 to 10 is inclusive.

You can do a for-loop like this.
for(var/i in 1 to 10)
world << i

You can even do steps of more than 1 at a time like this
for(var/i in 1 to 10 step 2)
world << i

Re: Intricacies of byond

Posted: Sun Jul 27, 2014 11:37 pm
by oranges
MisterPerson on github wrote:
input() won't crash if you include a default value or allow null using syntax like input(usr, "Choose one of the following", "Choose!") as null|anything in list("one", "two", "red", "blue"). In fact in general all input() procs should allow null or have a default because otherwise they runtime if the user disconnects with the input open.
Nabbing this to here from github since it could be useful.
https://github.com/tgstation/-tg-station/pull/4198

Re: Intricacies of byond

Posted: Thu Aug 14, 2014 1:57 am
by oranges
. in BYOND is a special var that will be returned automatically at the end of the proc if there is no return statement.

You can assign things to this i.e
. = list()

Courtesy of #coderbus

Re: Intricacies of byond

Posted: Thu Aug 14, 2014 2:36 pm
by Miauw
If you use /* and */ for multi-line comments, BYOND will ignore */ if it's behind a //
aka this won't compile and throw a "comment reached end of file" error:

Code: Select all

/* wtf who made this this is terrible
if(src == "a huge faggot")
    butt = "fart" //shitty workaround -some dude */

Re: Intricacies of byond

Posted: Thu Aug 14, 2014 10:05 pm
by MisterPerson
However, BYOND will not ignore multiline comments contained within other multiline comments.

For example, this will also throw an "end of file reached inside of comment" error:

Code: Select all

/* First set   /* Second set    */ End of second set

Re: Intricacies of byond

Posted: Wed Jan 13, 2016 8:57 pm
by Remie Richards
Necroing ancient thread at the request of oranges.

I wrote this https://github.com/tgstation/-tg-statio ... irkstricks recently, which covers some Performance related byond quirks.

Re: Intricacies of byond

Posted: Thu Jan 14, 2016 1:16 pm
by Ricotez
I don't get what you are trying to say about the dot operator at all

Re: Intricacies of byond

Posted: Thu Jan 14, 2016 1:43 pm
by Remie Richards
Ricotez wrote:I don't get what you are trying to say about the dot operator at all
It's also a temporary variable present in all procs and automatically returned at the end of a proc, and you can use it to avoid:
  • Defining your own var
  • The return statement (small overhead)
It also obeys operators like ++ and [], since it's just another variable, it just looks weird to see .[] or .++ despite it being perfectly valid.

Re: Intricacies of byond

Posted: Thu Jan 14, 2016 2:13 pm
by Ricotez
oh so if you just use . to store the variable you want the proc to return you'll save computation time?

Re: Intricacies of byond

Posted: Thu Jan 14, 2016 3:34 pm
by LiamLime
Assuming I'm understanding the amount of difference it makes to use the dot variable over the return statement+variable declaration, I'd still opt for variable declaration + return. All things being equal, the way I order code properties is:
correctness > readability > efficiency
The only time when efficiency overtakes readability is in academic work and general-purpose algorithms (machine learning, for example)
The only time when efficiency overtakes even correctness is in specialized general-purpose algorithms, which perform a general purpose task on only a subset of possible problems. (For example, some cases of NP-hard problems can be solved in polynomial time if the problems have certain properties. But the algorithm that solves them can only do so if the cases have these properties).

When it comes to games, apps, websites or servers, efficiency is the lowest of priorities. Don't get me wrong, I won't use bubble sort if quicksort exists, but I won't write a non-general-purpose script (for example an inventory system) in a hardcoded spaghetti magic number way, just because I can save some CPU time in variable assignments.

That's the theory though. When it comes to this specific dot thing, it's nowhere near as big a problem as the example I gave. There is actually even a language that does it in a similar way: Pascal. In pascal functions, the function's name becomes a variable within the function and is returned at the end. It is however quite likely that new coders will be more familiar with return than the pascal way, which is why I think it should probably be the way that's taught to them as the "usual way". Using ". = val" is just confusing, and it's hard enough to explain what ..() does, adding another of these dumb byond syntax things as a requirement would make the language even less appealing for people to learn.

I guess what I'm saying is... It might be a slight improvement, but probably nowhere near big enough to make its use a requirement, especially since it lowers code readability.

Re: Intricacies of byond

Posted: Thu Jan 14, 2016 3:42 pm
by MisterPerson
The difference between . = 1 and return 1 are so minor that if it was really the biggest waste of cycles, I would sooner suggest starting from scratch with C# since we'd be done with the game.

Re: Intricacies of byond

Posted: Thu Jan 14, 2016 5:55 pm
by Remie Richards
These aren't in the requirements or rules, but If I spot them I will ask you to correct them, and as for other things like the loops other maintainers have pointed them out too.
Kor has started writing some of these improvements BEFORE making his PRs, if Kor can pick it up anyone can :P

Re: Intricacies of byond

Posted: Thu Jan 14, 2016 8:36 pm
by Ricotez
if we'd start using it universally it would make it instantly obvious what variable is the most important one in every proc

Re: Intricacies of byond

Posted: Thu Jan 14, 2016 8:54 pm
by LiamLime
As ever, my concern is with the novice coder. Those of you who are already familiar with SS13 code would have no problem adapting, but it would mean that making those very first steps into SS13 coding would be just a bit much more confusing for the novice coder. Again, explaining ..() already takes a non-negligible amount of time, explaining . = val in addition to thiw would just make it take longer, and thus potentially result in losing even more coders to the early learning curve. I have no idea how noticeable this effect would be though, I kinda don't think it'd be negligible though.

Re: Intricacies of byond

Posted: Thu Jan 14, 2016 9:10 pm
by Remie Richards
LiamLime wrote:As ever, my concern is with the novice coder. Those of you who are already familiar with SS13 code would have no problem adapting, but it would mean that making those very first steps into SS13 coding would be just a bit much more confusing for the novice coder. Again, explaining ..() already takes a non-negligible amount of time, explaining . = val in addition to thiw would just make it take longer, and thus potentially result in losing even more coders to the early learning curve. I have no idea how noticeable this effect would be though, I kinda don't think it'd be negligible though.
Personally I believe it changes nothing, yes ..() is relatively complex, yes . = val is relatively complex, but the . = ..() construct exists FREQUENTLY in code, and they're going to come across that too, this is simply a perfect opportunity to explain . = val by piggy backing off what we already need to teach about ..()

Re: Intricacies of byond

Posted: Fri Jan 15, 2016 1:01 am
by Scott
I believe that when a novice coder has need of the . or to call ..() they will be given that information and they will immediately understand it. There is no point in trying to teach people ahead of time and no point worrying about people not understanding things, when the need to know arises they will easily learn.

Re: Intricacies of byond

Posted: Fri Jan 15, 2016 7:35 am
by LiamLime
Novice coders start by reading code, not by writing, so needing to understand ..() is a requirement from day one. If ". = val" replaced "return val" everywhere, then understanding ". = val" would become a day one requirement aswell.

If what Remie says is true, then this is however proably already the case. I don't think it was back when I learned TGS code though.