mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-11 15:14:27 +01:00
Centralize developer documentation. (#26485)
* Documentation. * Documentation. * Testing reqs update * Post feature-freeze code of conduct updates * spell checking * Style Guidelines * wrap * link up, bring headers up one level * wrap * fix old link * support github admonition syntax for mkdocs * link rules, rename to guard clauses * ffffucking vscode --------- Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
1b2b8434bd
commit
1d538adc1d
@@ -0,0 +1,304 @@
|
||||
# Guide to Debugging
|
||||
|
||||
## Intro
|
||||
Got a bug and you're unable to find it by just looking at your code? Try
|
||||
debugging! This guide will teach you the basics of debugging, how to read the
|
||||
values and some tips and tricks. It will be written as a chronological story.
|
||||
Where the chapters explain the next part of the debugging process.
|
||||
|
||||
Be sure to look at [Getting Started](../contributing/getting_started.md) if
|
||||
you're new and need help with setting up your repo. Do also remember that all
|
||||
below here is how I do it. There are many ways but I find that this works for
|
||||
me.
|
||||
|
||||
### What Is Debugging
|
||||
> "Debugging is the process of detecting and removing of existing and potential
|
||||
errors (also called as "bugs") in a software code that can cause it to behave
|
||||
unexpectedly or crash."
|
||||
[(source)](https://economictimes.indiatimes.com/definition/debugging)
|
||||
|
||||
As you can see from this quote. It is a very broad term. This guide will use a
|
||||
code debugger to step through your code and look at what is happening.
|
||||
|
||||
## How To Debug
|
||||
We will be using [#15958](https://github.com/ParadiseSS13/Paradise/issues/15958) as an example issue.
|
||||
|
||||
### Finding The Issue
|
||||
First of all, you need to understand what is happening functionally. This
|
||||
usually gives you hints as to where it goes wrong.
|
||||
|
||||
Looking at the GitHub issue we can see that the author (luckily) wrote a clear
|
||||
reproduction. Without one we'd only be guessing as to where it goes wrong
|
||||
exactly. Here a tripwire mine activates when it is in a container such as a
|
||||
closed locker or a bag.
|
||||
|
||||
This gives us the hint that the trigger mechanism does not check if the object
|
||||
is directly on a turf. Using this hint we go look for the proc which causes the
|
||||
trigger to happen. Using [my previous guide's advice][contrib]
|
||||
we quickly find `/obj/item/assembly/infra`.
|
||||
|
||||

|
||||
|
||||
In the current file of `/obj/item/assembly/infra`, "infrared.dm", we can see a
|
||||
lot of different procs. We're only really interested in the proc which triggers
|
||||
the bomb.
|
||||
|
||||

|
||||
|
||||
We see that the proc `toggle_secure` starts and stops processing of the object.
|
||||
This gives us a hint as to where the triggering happens. Looking at
|
||||
`/obj/item/assembly/infra/process` we see that it creates beams when it is
|
||||
active. Those beams are functionally used to trigger the bomb itself.
|
||||
|
||||

|
||||
|
||||
Looking at the `/obj/effect/beam/i_beam` object we see that this is indeed the
|
||||
case.
|
||||
|
||||

|
||||
|
||||
`/obj/effect/beam/i_beam/proc/hit()` is defined here which calls `trigger_beam`
|
||||
on master. `hit()` in term is called whenever something `Bumped` or `Crossed`
|
||||
the beam. I found this by right clicking on the `hit` proc and choosing `Find
|
||||
All References`
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
So now we know what is causing the triggering of the bomb. We know that beams
|
||||
are sent when the bomb is active. And we functionally know that these beams are
|
||||
also sent when the bomb is hidden in a locker or bag.
|
||||
|
||||
[contrib]: ./quickstart.md
|
||||
|
||||
### Breakpoints
|
||||
Now we know what is happening we can start debugging. I have a suspicion already
|
||||
of what is the cause of the issue. Namely that the `infrared emitter` does not
|
||||
check the `loc` of the actual bomb in the `/obj/item/assembly/infra/process()`
|
||||
proc.
|
||||
|
||||
To confirm this I will place a breakpoint just when the `process` proc begins.
|
||||
You do this by clicking just left of the line number where you want to put the
|
||||
breakpoint.
|
||||
|
||||

|
||||
|
||||
The red dot there is a breakpoint that is set. Clicking it again removes it.
|
||||
|
||||
After doing this we will follow the reproduction steps. Once the game hits your
|
||||
breakpoint it will freeze your game and your VS Code instance should pop up to
|
||||
the front. If not just open VS Code.
|
||||
|
||||
When testing this I noticed that the breakpoint got hit multiple times before I
|
||||
could do my reproduction. If that is the case then you have multiple options for
|
||||
combating this annoyance. Either you disable the breakpoint and re-enable it
|
||||
when you are ready to test. This is not a great way of doing it for this case
|
||||
but in some cases, this is enough. Or you move the breakpoint to a better
|
||||
location. I choose this one and I've moved it a bit lower.
|
||||
|
||||

|
||||
|
||||
I moved it past the `if(!on)` check which eliminates all trip wires which are
|
||||
not turned on. (Quick note. This is really bad code. They should not be
|
||||
processing when they are not turned on)
|
||||
|
||||
I spawned a premade grenade instead of making my own grenade here. Saves me
|
||||
quite some time and annoyance in trying to look up how to do this again.
|
||||
Remember the search results when looking for the trip mine? Yeah, use one of
|
||||
those.
|
||||
|
||||
### In Debug Mode
|
||||
Once I turn on the bomb I notice that VS Code indeed pops up to the front. The
|
||||
game is now paused till you say it can continue.
|
||||
|
||||

|
||||
|
||||
The breakpoint line is now highlighted. The highlight shows where the code is
|
||||
currently. It is about to do the `if(!secured)` check. Let's make the code
|
||||
execute that one step by stepping over the line. F10 as a shortcut or you can
|
||||
press the "Step Over" button in your active debugger window.
|
||||
|
||||

|
||||
|
||||
Now the code is executed and the highlight moved to the next line. Step over is
|
||||
handy to quickly move over your code. It will jump over any proc call. Step Into
|
||||
(F11) is for when you want to actually step into the proc call. This will be
|
||||
explained later when it is needed.
|
||||
|
||||
If you step over a bunch of times you will see that it will go and create the
|
||||
`i_beam`.
|
||||
|
||||

|
||||
|
||||
Even though I am currently holding the bomb in my hands (same situation as when
|
||||
it is in a bag/locker code-wise). Why is this the case? In the code, you can see
|
||||
that a beam is created when the bomb is `on`, `secured` and `first` and `last`
|
||||
are both null. These all have nothing to do with our issue. But the last check
|
||||
checks if `T` is not null. `T` is defined earlier in the proc as
|
||||
`get_turf(src)`.
|
||||
|
||||

|
||||
|
||||
In other words. `T` is the turf below my characters feet. And of course, this
|
||||
one does exist in this case. What is missing here is a check to see if the
|
||||
actual bomb is on a `turf`. How do we even check this?
|
||||
|
||||
Time to go look for a reference to the actual bomb as `/obj/item/assembly/infra`
|
||||
is just the mechanism used by the bomb. `/obj/item/assembly/infra` itself is a
|
||||
subtype of `/obj/item/assembly` which is the base type that is used for bomb
|
||||
mechanisms. So we best look at the definition of `/obj/item/assembly`. We do
|
||||
this by `Ctrl`-clicking on the `assembly` part of `/obj/item/assembly/infra`.
|
||||
|
||||

|
||||
|
||||
Here we see the definition. We can see that `/obj/item/assembly` has a variable
|
||||
called `holder` which is of type `/obj/item/assembly_holder`. This is most
|
||||
likely the thing we want.
|
||||
|
||||
Now to check if this is correct. Go to your debug window and open `Arguments`
|
||||
and then `src`. `src` is of course the object we currently are. Which is the
|
||||
`/obj/item/assembly/infra`. Once it is open you can see a **LOT** of variables.
|
||||
|
||||

|
||||
|
||||
We are not interested in most of them and instead, we want to find `holder`
|
||||
|
||||

|
||||
|
||||
Yep, this is the one we want. Now how do we check if this object is directly on
|
||||
a turf? How do we even check its location? The answer is `loc`. `loc` contains
|
||||
the location of the object. Here I found out that the `loc` of the
|
||||
`assembly_holder` wasn't actually my character but instead it was the grenade.
|
||||
Good thing we checked further before we started coding right?
|
||||
|
||||

|
||||
|
||||
### Finding/Making The Right Variable To Use
|
||||
Now it becomes a bit odd. Chemistry bomb code is fairly ... bad. But we can make
|
||||
this work. First, we go look a bit more into the code to find the proper
|
||||
variable to use. We *can* use `holder.loc.loc` but that says very little about
|
||||
what it actually means. It would cause even more headaches for people working
|
||||
with the code in the future. Instead, we will help our future co-developers a
|
||||
bit and look into improving the existing code. Later on, we also see that this
|
||||
is not the correct way of fixing it fully.
|
||||
|
||||
Let's take a look at the `assembly` code in `assembly.dm`. Let's see how the
|
||||
assembly determines what its grenade is. When looking around the file I found
|
||||
the proc `/obj/item/assembly/proc/pulse(radio = FALSE)`. This one seems
|
||||
promising.
|
||||
|
||||

|
||||
|
||||
Here we can see that either the holder is used or if `loc` is a grenade it will
|
||||
be primed using `prime`. As you can see a previous coder even stated that this
|
||||
is a hack. This however does give me an idea of how to handle this and the edge
|
||||
cases that exist. Namely the case where a grenade owns the trip laser as a
|
||||
mechanism.
|
||||
|
||||
The idea I had in mind is to create a proc which returns the physical outer
|
||||
object. The payload, grenade or such. Where does this proc go? Well in the
|
||||
assembly file since it will be usable for other code as well. I just put it at
|
||||
the bottom of the file since nowhere else seemed to fit better. I quickly found
|
||||
that I needed some more info for this. What if the `holder` was not attached
|
||||
yet? Or it is remotely attached to a bomb? For this I made a proc for the
|
||||
`/obj/item/assembly_holder` object which will return the actual outermost
|
||||
object.
|
||||
|
||||

|
||||
|
||||
I found out that `master` is the bomb linked by the line at the top of the
|
||||
image. This all allows me to complete my other proc.
|
||||
|
||||

|
||||
|
||||
Now we have a proper method to use to find the outermost object.
|
||||
|
||||
### Applying The Fix And Testing It
|
||||
Now we can go and fix the issue at hand. We want to check if the outermost
|
||||
object its `loc` is a turf. If not it should not fire new lasers and kill the
|
||||
old ones.
|
||||
|
||||
We already have the turf that the `/obj/item/assembly/infra` is located on. Now
|
||||
we just have to check if that turf is the same turf as our outermost object.
|
||||
|
||||

|
||||
|
||||
Now, this should work. But you of course have to test it! Build it and run the
|
||||
game. Run it without a breakpoint set first to see if it works functionally.
|
||||
|
||||
And it seems to work! Now let's trip it and see if it actually keeps working.
|
||||
|
||||
### Runtimes And Stacktrace Traveling
|
||||
And the game froze. VS Code began blinking. What is happening? The game ran into
|
||||
a runtime exception
|
||||
|
||||

|
||||
|
||||
Now let's see if this is actually our fault or not. Since we did not touch any
|
||||
timers. To check this go to the debug window and open the call stack.
|
||||
|
||||

|
||||
|
||||
Here you can see **ALL** the current "threads" of the game. DM itself is not a
|
||||
multithreaded language but it can have multiple threads. I won't go into detail
|
||||
on that here for simplicity sake. The top item is the one you are currently on.
|
||||
Just click on it to open it.
|
||||
|
||||

|
||||
|
||||
Here we can see the entire stack trace of our current thread. The stack trace is
|
||||
the entire path the code took thus far. At the top is the last called proc and
|
||||
at the bottom is the origin of the call. Clicking on each stack will jump you to
|
||||
the location in the code. The message said that `addtimer` was called on a
|
||||
`QDELETED` item. This means that the item it is made for is already deleted.
|
||||
Lets click on `/obj/item/assembly/infra/trigger_beam()` in the stack trace to go
|
||||
to that call location.
|
||||
|
||||

|
||||
|
||||
Here we see where it went wrong. It seems that `addtimer(CALLBACK(src,
|
||||
.proc/process_cooldown), 10)` is called even though the `src` is already
|
||||
deleted. How can this happen? To save us all some time. `pulse` is the proc
|
||||
which triggers the bomb. In our case our bomb exploded, destroying itself.
|
||||
Now... is this our fault? No. But we can fix it nonetheless.
|
||||
|
||||

|
||||
|
||||
This code change will ensure the message is sent and that the runtime stops from
|
||||
happening.
|
||||
|
||||
### Stepping Into VS Stepping Over
|
||||
Now back to the testing. Once build again start up the game again and try it
|
||||
this time using a raw `/obj/item/assembly/infra`. As you can see it works only
|
||||
when you hold it. But not when it is on the floor itself. Seems we made a
|
||||
mistake! Place a breakpoint again in the `/obj/item/assembly/infra/process()`
|
||||
proc since there something goes wrong.
|
||||
|
||||

|
||||
|
||||
Now we want to step into the current line. This means that we will go into
|
||||
`get_outer_object`. Press either F11 or the "Step Into" button.
|
||||
|
||||

|
||||
|
||||
This will get us to the proc itself. Now step over till you see it return `loc`.
|
||||
`loc` here is the turf. Which is not the outermost object. Seems we need to do
|
||||
another check there. This almost certainly also goes for
|
||||
`/obj/item/assembly_holder/proc/get_outer_object()` as we used mostly the same
|
||||
logic there.
|
||||
|
||||

|
||||
|
||||
## Outro
|
||||
Now let's test again. And it seems that there are more issues! The
|
||||
assembly_holder still shoots a laser if you hold it or put it in a bag. Same for
|
||||
the infra itself. Now I know the fix already but where is the fun in that.
|
||||
|
||||
I'm leaving the last solution (and honour of making the PR that solves the
|
||||
issue) open for you! If you feel like testing yourself then please pick up this
|
||||
issue and fix it the way you think would work. I'd love to see your method!
|
||||
|
||||
Please also let me know what you think of this format of doing a guide. This one
|
||||
was more a look into how I do it step by step compared to a more structured
|
||||
guide.
|
||||
Reference in New Issue
Block a user