goto
. Bad.:
operator to override type safety checks. Instead, cast the variable to the proper type.del
, it's horrendously slow. Use qdel()
.<>
, it's completely unused in the land of SS13. Use !=
instead, as it's infinitely more sane.SPAWN()
instead of spawn()
TIME
instead of world.timeofday
&
) - Write as bitfield & bitflag
'foo.ogg'
instead of "foo.ogg"
for resources unless you need to build a string (e.g. "foo_[rand(2)].ogg"
).FALSE
and TRUE
instead of 0
and 1
for booleans.x in y
rather than y.Find(x)
unless you need the index.
When possible, we always want people to document their code clearly so that others (or perhaps yourself in the future) can understand and learn why a piece of code was written.
Unless the code is extremely complex, what one generally wants to comment is the motivation behind a certain piece of code, or what it's supposed to fix - rather than what it's actually doing.
Additionally, we have a 'documentation comment' system set up. This is as simple as commenting code like:
By using this, when you hover over a variable or object, it'll display information in the comment. For example:
For more detailed information on this system, see the DMByExample page.
The codebase contains some defines (e.g. SECONDS
) which will automatically multiply a number by the correct amount to get a number in deciseconds. Using these is preferred over using a literal amount in deciseconds.
The codebase also has defines for other SI units, such as WATTS
. There are also SI unit prefixes for use, such as MILLI
. These should be used whenever you're dealing with a quantity that's a SI unit. If you're using a derived unit, add its formula to the defines. These can be chained like so: 100 MILLI WATTS
or 1 KILO METER
.
Don't use numbers that have no explanation behind them. Instead, it's reccomended that you either put it into a const variable, a local file #define, or a global #define.
Bad:
Good:
If you don't need the define anywhere outside this file/proc, make sure you undefine it after:
We don't want dozens of nesting levels, don't enclose a proc inside an if block if you can just return on a condition instead.
Bad:
Good:
foo.len
vs. length(foo)
Our codebase uses the latter, the length(foo)
syntax.
The .len
syntax runtimes on a null foo
, whereas the length()
syntax will not.
It's also faster (~6%), for internal bytecode reasons (which don't really matter).
Some types exist just as a parent and should never be created in-game (e.g. /obj/item
). Mark those using the ABSTRACT_TYPE(/type)
macro. You can check if a type is abstract using the IS_ABSTRACT(/type)
macro.
To get a list of all concrete (non-abstract) subtypes of a type you should use concrete_typesof(type)
, the result is cached so no need to store it yourself. (As a consequence please .Copy
the list if you want to make changes to it locally.) Proper usage of ABSTRACT_TYPE
+ concrete_typesof
is preferred to using typesof
and childrentypesof
usually though exceptions apply.
If you want to filter the results of concrete_typesof
further (e.g. by the value of a var or by a blacklist) consider using filtered_concrete_typesof(type, filter)
. filter
is a proc that should return 1 if you want to include the item. Again, the result is cached (so the filter
proc should not depend on outisde variables or randomness).
Example:
See _stdlib/_types.dm
for details.
See: if(x)
vs if (x)
Nobody cares about this. This is heavily frowned upon for changing with little to no reason.
Always put spaces after commas or operators, and before operators:
Bad:
Good:
For long lines, use \
at the end of the line to split onto multiple lines.
With the recommended extension pack, a vertical line will appear in the editor; if your code goes far beyond this line, it's time to split the code into multiple lines. Put a space before \
.
When doing this, indent each line past the first to 1 tab past the start of the right side of the definition (or whatever is similar- for proc calls, 1 tab past the proc name, etc).
Bad:
Good:
Lists follow a similar pattern, but \
isn't required.
Bad:
Good:
For lists of long expressions, 1 line per expression is fine.
Good:
src
In procs which are called on an object (i.e. everything but global procs), src
is the object which the proc is being called on. To reference a variable on this object, you should always use src.varname
. Simply referencing varname
(leaving the src
implicit) should be avoided, as it is less clear and leads to bugs while refactoring.
The same goes for procs- to call a proc on the same object, use src.procname()
.
Bad:
Good:
Very short (1-2 character) variable/argument names are acceptable only if they are the standard names for common types.
/atom
- A
/atom/movable
- AM
/obj
- O
/obj/item
- I
/obj/item/grab
- G
/mob
- M
/mob/living
- L
/mob/living/critter
and /obj/critter
- C
/mob/living/carbon/human
- H
/turf
- T
Names should be descriptive unless the context and type make it immediately clear what the variable is used for. This includes the above names- only use them if it is clear what the variable is for.
Bad:
Good:
First, read the comments in this BYOND thread.
There are two key points there:
Remember: although this trade-off makes sense in many cases, it doesn't cover them all. Think carefully about your addition before deciding if you need to use it.
When dealing with iterating over lists, you generally have two cases: where a list will only contain one type, and where a list will contain a multitude of types.
For the first case, we can do some special optimization, in what we call a "typecheckless for-loop."
The syntax looks like this:
This ends up giving us a 50% increase in speed, as with a normal typed for-loop it performs an istype(thing, obj/foo)
on the object every iteration.
Be warned: If something in the list is not of the type provided, it will runtime! This includes if you try to access a value on a null.
Additional note: If you are using by_type[]
, there exists a macro to do this automagically:
As long as you don't want to filter out between specific children types of a by_type, you should be able to use this construction.
for (var/i = 1, i <= some_value, i++)
is the standard way to write a for-loop in most languages, but DM's for(var/i in 1 to some_value)
syntax is actually faster in its implementation.
So, where possible, it's advised to use DM's syntax. (Note: the to keyword is inclusive, so it automatically defaults to replacing <=
; if you want <
then you should write it as 1 to some_value-1
).
Be Warned: if either some_value
or i
changes within the body of the for (underneath the for(...)
) or if you are looping over a list and changing the length of the list then you cannot use this type of for-loop!
Almost all of the time when iterating through lists, we use the for (var/i in some_list)
syntax. However, in some very few cases, we run into a performance issue.
Internally, BYOND copies the some_list
for this operation, so that when you are iterating through the list, you don't skip items or run into things twice if you modify the list inside the loop.
However, this can cause performance issues with large lists of complex objects, generally greater than ~5000. There exists a performance optimization, but bear in mind it's only applicable if you are traversing less than half of the list. Perhaps you are breaking after a found item that's randomly in the list, or you only want to process the first 20 entries or something.
Code that avoids this list copying would look like:
.
)Like other languages in the C family, DM has a .
or "dot" operator, used for accessing variables/members/functions of an object instance. For example:
However, DM also has a dot variable, accessed just as .
on its own, defaulting to a value of null. Now, what's special about the dot operator is that it is automatically returned (as in the return statement) at the end of a proc, provided the proc does not already manually return (e.g. return x
)
With .
being present in every proc, we use it as a temporary variable. However, the .
operator cannot replace a typecasted variable - it can hold data any other var in DM can, it just can't be accessed as one, although the .
operator is compatible with a few operators that look weird but work perfectly fine, such as: .++
for incrementing .
's value.
DM has a variable keyword, called global
. This var keyword is for vars inside of types. For instance:
This does not mean that you can access it everywhere like a global var. Instead, it means that that var will only exist once for all instances of its type, in this case that var will only exist once for all mobs - it's shared across everything in its type. (Much more like the keyword static
in other languages like PHP/C++/C#/Java)
Isn't that confusing?
There is also an undocumented keyword called static
that has the same behavior as global but more correctly describes DM's behavior. Therefore, always use static
instead of global
in variables, as it reduces surprise when reading code.
Typecasting in for
loops carries an implied istype()
check that filters non-matching types, nulls included. The as anything
keyword phrase can be used to skip the check.
If we know the list is supposed to only contain the desired type then we want to skip the check not only for the small optimization it offers, but also to catch any null entries that may creep into the list.
Nulls in lists tend to point to improperly-handled references, making hard deletes hard to debug. Generating a runtime in those cases is more often than not positive.
Bad:
Good:
usr
keywordusr
, in a general sense, is "the mob that caused this proc to be invoked". It persists through an arbitrary number of nested proc calls. If something wasn't caused by a mob, usr
is null.
usr
is required by verbs, which are commands specifically invoked by a mob, and is needed to apply things to the calling mob.
Outside of verbs (every other proc), usr
is extremely unreliable. An excellent example of this is that if someone hooks a pressure sensor to a gun, and then you step on the pressure plate, you are the usr
for that gunshot.
Instead of using usr
, pass the user mob into your proc as an argument.
Bad:
Good:
as mob
, as obj
, etcIn verbs, when invoked from the command bar these allow the user to autofill results.
Outside of verbs, they do nothing and should be removed.
Bad:
Good:
Additional note: This applies in general to anything
used as a verb, which can be any proc added to an atom
via atom.verbs += /proc/x
.
So, be careful when removing as x
to
make sure it isn't being used as a verb somewhere else.
According to the DM docs, using round(A)
with only one argument is deprecated (not approved). The correct way to use round()
is to do round(A,B)
, where B is the nearest multiple. For most cases, this will be 1
, although sometimes you'll need it to the nearest 10 or 5.
What happens if you do round(A)
then? Guess what? It floors the value, and is equivalent to floor(A)
. It rounds it down. Thanks BYOND.
So in general, use round(A,1)
for your rounding needs.
.git-blame-ignore-revs
Run git config blame.ignoreRevsFile .git-blame-ignore-revs
in the root goonstation folder to ignore commits that touched a lot of files for big formatting reasons. This helps deal with git blame
a bit easier, and you only need to run this once.
You can check out a guide on using the debugger in the guide located in the Developer Guide.
__build.dm
, and other local code changesIf you're tired of having to constantly add & remove #define IM_REALLY_IN_A_FUCKING_HURRY_HERE
and similar from __build.dm
, along with stashing and re-stashing your own development tools, there's a solution!
Create a file named __build.local.dm
right next to it. Named exactly that.
This file will not get picked up by Git, and will let you keep whatever defines you want in there.
There is additional support for a file called __development.local.dm
, which is included in goonstation.dme
after the build defines so that you can add new testing tools for yourself there. Be sure to create this file in the right directory!
Specifically, it needs to be created under code/WorkInProgress/__development.local.dm
. Additionally, there is a build flag, DISABLE_DEVFILE
, which will stop this from loading. Use this before asking for help if your local development environment is breaking! And on the flip side, if your code changes are not showing up from this file, make sure this flag is disabled.
Most importantly of all, be sure that you remember you put your configs and/or code into those locations! Please don't come to #imcoder asking why your local setup always compiles Nadir 😸, or why your map is full of capybaras.
The Debug-Overlays verb ingame is your friend. It offers many modes to debug many things, such as atmos air groups, writing, areas, and more.
The Open-Profiler verb ingame is also your friend. Be sure to literally type .debug profile
in the second box.
Once you refresh once, you'll get detailed performance measurements on all running procs.
Guide to the categories:
If total cpu and real time are the same the proc never sleeps, otherwise real time will be higher as it counts the time while the proc is waiting.
There exists a project to provide an incredibly more advanced real-time profiler for DM, named goon-byond-tracy, capable of providing incredible resolution.
To operate this, you will need to do three things: download the tracy 'viewer' application v0.11.x, and either compile or download the byond-tracy library.
#define TRACY_PROFILER_HOOK
in _std/__build.dm
If you're on Linux you need to compile both yourself manually, obviously.
You can spawn in a target dummy (/mob/living/carbon/human/tdummy
) to more easily test things that do damage - they have the ass day health percent and damage popups visible even if your build isn't set to ass day.
ninjanomnom from TG has written up a useful primer on signals and components. Most of the stuff there applies, although elements do not exist in this codebase.
Hate coding action bars? Making a new definition for an action bar datum just so you have visual feedback for your construction feel gross? Well fear not! You can now use the SETUP_GENERIC_ACTIONBAR() macro!
For private action bars (only visible to the owner), SETUP_GENERIC_PRIVATE_ACTIONBAR() does the same.
Check _std/macros/actions.dm for more information.
Making multiple turfs can be a real pain sometimes. If you use the DEFINE_FLOORS()
macro as documented, it will create a simulated, simulated airless, unsimulated and unsimulated airless turf with the specified path and variables at compile time. There are many variations on the definition, so I recommend checking out _std/macros/turf.dm
If you want to track everything of a type for whatever reason (need to iterate over all instances at some point, usually), use the tracking macros. Add START_TRACKING
to New()
(after the parent call), and STOP_TRACKING
to disposing()
(before the parent call). To get all tracked objects of a type, use by_type[type]
.
To track a group of things which don't share the same type, define a tracking category and then use START_TRACKING_CAT(CATEGORY)
and STOP_TRACKING_CAT(CATEGORY)
in the same way as above. To get all tracked objects of a category use by_cat[CATEGORY]
.
VERY VERY BAD:
Good:
The passability cache unit test enforces rules associated with pass_unstable
, a variable that is used to optimize pathfinding. It prevents the implementation of Cross
and other collision-based callbacks for all types that declare pass_unstable = FALSE
.
In this situation, the unit test detected a forbidden proc Cross
on /obj/machinery/silly_doodad
. Because /obj/machinery
declares pass_unstable = FALSE
, your subtype must also follow the rules. In this situation, you have two options:
Cross
implementation, orpass_unstable = TRUE
, which may cause performance degredation, especially if the atom in question frequently appears in-game.The same rules apply to any procs the test detects. For example, if it says must not implement Enter
, then replace Cross
with Enter
in the above section.
These two procs do two similar but distinct things. Cross
returns whether or not a given movable can cross src
, while Crossed
is called whenever a given movable crosses src
.
If you only need to cause something (i.e. your machine heals people when someone steps on it), then you want to use Crossed
instead.
Here's a quick list of all the side-effect versions of movement callbacks.
Cross
-> Crossed
Enter
-> Entered
Exit
-> Exited
Uncross
-> Uncrossed
This failure happens when you set pass_unstable = FALSE
on a subtype of something that implements a forbidden proc. In most cases, there's nothing you can do except follow the section above for the offending parent type. In this case, you would look at /obj/machinery/unstable_thingy
to see if you can make it stable.