Return to “Dev Logs”

Post

[Adam] Thursday, February 15, 2018

#1
Allllright. It's been a little while since I've rambled on the forum so I figure it's time for an update. Plus, with Lindsey down for maintenance and Josh defragging the system someone's gotta keep y'all in the loop, right?


It looks like PAX was a little more draining than we expected. I'm certainly not complaining, because we had a great time jamming on the demo and having so many excited people hanging out at the booth all weekend. Committing to writing only gameplay code for a week and getting to see just how well all the moving parts are operating together has been a massive kick in the pants in terms of motivation and excitement. We were able to absolutely slam through features and have everything Just Work.

That said, I think everyone is finishing up a little recharge. December and January have been hectic. Between travelling for Christmas, jamming for PAX, presenting at PAX, the Global Game Jam, and moving we're all taking it a little bit easy right now.

What have I been up to since you last heard from me, you ask? Well, let's see..

UI Iteration
Since the original implementation of the dev UI I've made some really nice simplifications and improvements. In fact, Josh decided to move forward with the dev UI and the final gameplay UI. That meant I had to do some more hammering to ensure it was up to snuff. I massively simplified how focus and dragging are handled. I made sure that the UI does sane things when widgets are removed at runtime. I added tons of polish: keyboard and gamepad navigation, ScrollViews always keep the current selection in view (even if you were to nest multiple ScrollViews inside each other), fixed the last few subtle 'off-by-a-pixel' type issues, and started pulling over Josh's shader-based UI drawing code. That's right, just because it's programmer art right now doesn't mean it's staying that way. It's super easy to swap out the drawing code with proper prettified versions.


General Infrastructure
I tend to alternate between big tasks and little tasks to keep my mind fresh so I frequently bounce around making little things better. We now have proper signal handling in the engine. In unexpected/unsupported situations the engine simply aborts. Previously this meant the game just printed a message and closed. If you had a debugger attached you'd have a chance to walk up the call stack and determine what happened, but sometimes you don't have the debugger running (or never, if you're a masochist like Josh). Now we can easily register handlers from any system that will run any time the game crashes. A particular pain point was Lua. Being a dynamic language it's super easy to write silly bugs that will cause a crash (like type the wrong variable name when calling into the engine and accidentally passing a null pointer instead of, say, the mesh you want to render). Now we're able to automatically print the entire Lua stack when something goes wrong which instantly cut our bug fixing turn around time by an order of magnitude. 9 out of 10 times we know exactly which line caused the problem without having to think about it.

I also went through the engine to standardize and organize some of it a bit better. We had various APIs with unintuitive names to avoid name collisions with other common libraries. So things like StrUtil, MemUtil, and LibMath became PhxString, PhxMemory, and PhxMath. I also ripped out some templates in favor of good old function overloading.

Before we had a solid Lua foundation we used a second C++ project called scratch where we did explorations and wrote test applications that stressed specific aspects of the engine. In particular, that's where my (fairly large) BSP testing application was. Now that we have robust Lua tooling we write our tests in Lua based applications that actually live in the LT project and we're able to easily choose which application to launch. That scratch project has been sitting there unused and collecting dust so I rewrote the BSP tests in Lua and nuked scratch entirely.

I also added some niceties to our Windows build environment. I massively trimmed the output so it doesn't spam the console with things we don't care about, added a brightly colored message for build success/failure, profiled build times in an attempt to cut them down (turns out, vcvarsall.bat takes longer than compiling and optimizing over 16,000 significant lines of code, thanks Microsoft!), and output colorized warnings and errors in realtime. I think I finally learned my lesson this time: Command Prompt / Batch files suck. I'm done with them for good this time. I'm going to pick a new language for tools.


Input
Here's where I've been spending most of my time recently and where I think it would be fun to dive in a little.

Input is something I've wanted to sit down and really solve for quite a while now. There's a lot of subtlety surrounding input and finding a way to handle the complexity while maintaining a level of elegance is a worthy challenge. In my last game I dealt with input as 2 separate type: Buttons and Axes. Buttons were a binary thing, either pressed or not. Axes were an analog thing, a value in the range [-1.0, 1.0]. The biggest issue here is that you have to decide in advance what is a button and what is an axis and you have to stick to that. You can't treat the same input as a button in some places and an axis in others. For example, the left analog stick on a controller wants to be treated like an axis when you're moving your character, but in the main menu you want to translated it to button presses for Up, Down, Left, Right. My way around it at point was to have wrappers that converted between input types in the actual bindings. The types ended up looking something like this:

Spoiler:      SHOW

Code: Select all

interface IButton
{
  bool IsPressed  { get; }
  bool IsHeld     { get; }
  bool IsReleased { get; }

  void Update();
}

interface IAxis
{
  float Value      { get; }
  float Deadzone   { get; }
  bool  IsInverted { get; }

  void Update();
}

class ButtonToAxis : IAxis
{
  private IButton button;
  // ...
}

class AxisToButton : IButton
{
  private IAxis axis;
  // ...
}

Obviously this was back when I was a lot OOP-ier (read: wronger). There are lots of things I don't like about this now: bindings are doing actual input processing instead of just mapping values to actions, it's a polling interface, it has interfaces, inheritance, and virtual functions...

The polling style is the biggest issue. You end up checking every possible action every frame. And what happens when a button is pressed twice in the same frame (unlikely, but possible with low framerate when typing text)? How would you record and replay input to implement a replay feature for gameplay or debugging purposes? If you're serializing bindings in order to save them you now have to store information about this ButtonToAxis/AxisToButton business. It doesn't feel like the right solution.

What if, instead of asking if a button is pressed, we stick events in a queue for every change in input. Then gameplay loops over the input queue and runs the necessary logic for each button press or axis change (I stole this idea from the Quake engine). That feels like a step in the right direction.

So my first attempt at improving our input handling was to implement an InputStream API. Here, everytime the engine detected an input change it stuck an InputEvent into the stream. From the gameplay side we'd loop over all events at the top level of the game loop and call OnInput on everything that was currently consuming input. LuaJIT was not a big fan of this loop. And it didn't solve the button vs axis issue. But, we did get gamepad support as part of this change. And we no longer missed multiple presses in the same frame or a press and a release in a single frame. However we still had to treat buttons and axes differently and we were shoving extra information into the events like the mouse position/scroll and the current modifier keys. Still, that's a net win and this is the system we used at PAX.

Then, Josh gave me 2 small nudges to see the full solution: 1) why are we sending input events to everyone instead of the just the place that uses it, and 2) what if everything was an axis? I took 2 and ran with it a bit: what if everything was both a button and an axis? All input, regardless of its source starts off as an analog value. Then we take that analog value and interpret it as a button also. Let's say when the value is greater than 0.5 it's pressed and less than 0.4 it's released. We store both and let gameplay decide which representation it wants.

At this point, we still needed a proper solution for remappable bindings, support for more than just the Xbox 360 controller we used a PAX as well, HOTAS support, and haptic feedback. However, we still wanted to keep the ability to read input without setting up bindings and to query input directly without using the event loop. When we're writing a test application or we're prototyping gameplay we don't want to have to think about bindings and events we just want to do the braindead thing and ask if the space bar is pressed. We needed to keep all the existing functionality of the 'inferior' input APIs (at this point we're up to 3 different input APIs)

Thus, for the past few days I've been rewriting the input system to incorporate all these ideas into the One True Input API that deprecates all the others, solves every problem, and ends world hunger. I think I've accomplished the goal (well, am currently accomplishing. There's a little more to go). I ended up splitting the input system into 2 parts: a low level API (Input) and a high level API (InputBindings).

The low level API is as simple as possible. It reads input from the system, keeps track of everything as both buttons and axes, and pushes events into a queue. Because it stores a copy of the current input we're able to do the direct queries: Input.GetButtonPressed(Button.Space) but it also stores the number of button transitions during the last update so it won't miss double presses or press and release in the same frame. It leverages SDLs ability to virtually map all controllers to a standard Xbox 360-like layout so just about any controller you plug in will Just Work. It ensures press and release events are perfectly paired (even if your controller is remapped while holding a button, much to Josh's annoyance). This API is extremely robust and basically complete. Everything LT related has been migrated over. It needs slight improvements to the way modifiers are handled, haptic feedback support, and to be tested with HOTAS / unmapped controllers.

Spoiler:      SHOW
Image

The high level API is where bindings and the callback magic happen. Multiple inputs from any device can be bound to an action. Actions 'eat' the input so only the most recently added action will be triggered which allows us to push sets of actions onto a stack. Callbacks for specific actions (e.g. OnPressed or OnValueChanged) can be registered for from Lua and code will be called directly rather than have to query or process events. The high level API is meant to mirror the low level API as much as possible (making it painless to move from the low level API to the high level one) so it supports both direct query and an event queue in addition to the callback mechanism. The API is mostly implemented but we need to flesh out the callback mechanism, the syntax for creating bindings/registering callbacks, and explicit device assignment.

Honestly, I'm quite proud of how this is turning out. I feel like I've 'solved' input and will be able to use this approach for future projects. At the very least it's nice to have reached a point where we have one simple, unified way of dealing with input.


Misc
Other stuff I've been up to:

  • Integrated FMOD for audio support.
  • Attached very quiet cantina music to asteroids and watched Lindsey go slightly insane trying to figure out why she has Star Wars stuck in her brain.
  • Sniped revisions 1000 and 1024 from Josh and made really boring commits.
  • Played around with tooling for merging / viewing / debugging mods.
  • Moved in with my girlfriend.
  • Got a puppy!
  • Gave said puppy a trial run as head of the Office Happiness Enhancement Department
    (she'll be back for a second round of trials in March).


Phew. I've glossed over so much detail (like the ridiculously awesome procedural sound that's on the horizon) and this is still a wall of text. How about a picture of Tess to make up for it?

Spoiler:      SHOW
Image
Last edited by AdamByrd on Thu Feb 15, 2018 10:28 pm, edited 1 time in total.
Post

Re: [Adam] Thursday, February 15, 2018

#2
Very awesome post, Adam! :D I particularly loved this bit:
Attached very quiet cantina music to asteroids and watched Lindsey go slightly insane trying to figure out why she has Star Wars stuck in her brain.
:lol:

I was going to try to get you to make a post this Friday, seeing as Josh and Lindsey have been down for the count, but seems like you beat me to it. Hope they feel better soon, and make sure you stay healthy too! :D
Have a question? Send me a PM! || People talking in IRC over the past two hours: Image
Image
Image
Post

Re: [Adam] Thursday, February 15, 2018

#3
AdamByrd wrote:
Thu Feb 15, 2018 2:11 pm
If you had a debugger attached you'd have a chance to walk up the call stack and determine what happened, but sometimes you don't have the debugger running (or never, if you're a masochist like Josh).
Attached very quiet cantina music to asteroids and watched Lindsey go slightly insane trying to figure out why she has Star Wars stuck in her brain.
That has made my day so much brighter! :lol:

A nice solution on the axis vs button problem, very 'organic'. Human brain deals with it the same way as it's all analogue inputs. I also remember some ancient piece of Soviet hardware that used similar approach - buttons were simply causing inputs to go to their extremes :monkey:

An elegant solution...for a more civilised age :ghost:

:thumbup:
Image
Survivor of the Josh Parnell Blackout of 2015.
Post

Re: [Adam] Thursday, February 15, 2018

#5
AdamByrd wrote:
Thu Feb 15, 2018 2:11 pm
Honestly, I'm quite proud of how this is turning out. I feel like I've 'solved' input and will be able to use this approach for future projects. At the very least it's nice to have reached a point where we have one simple, unifed way of dealing with input.
what happens when theres heavy lag (for whatever reason), does it still catch all inputs and behave like expected or does it start dropping inputs?
Post

Re: [Adam] Thursday, February 15, 2018

#6
Cornflakes_91 wrote:
Thu Feb 15, 2018 2:50 pm
what happens when theres heavy lag (for whatever reason), does it still catch all inputs and behave like expected or does it start dropping inputs?
With the callback (which we'll be shipping on) and event loop usage patterns input is never dropped, no matter the framerate.

For direct queries, although we record every button transition (released to pressed or pressed to released) which means we catch _almost_ all input, you can't get the _number_ of presses/releases in a frame from the API. So if a button is pressed twice in the same frame the second press won't be handled. In theory we could make this possible very easily (just return an int from Input_GetPressed instead of a bool) but there's not a good justification for the complexity. We won't be shipping code using this pattern; it's only there to make development easier and it would result in _really_ ugly code like

Code: Select all

for i = 1, Input.GetPressed(Button.Space) do
 -- Do stuff...
end
Also direct queries don't preserve the order of events, so it's by definition not going to be a robust pattern.
Post

Re: [Adam] Thursday, February 15, 2018

#7
AdamByrd wrote:
Thu Feb 15, 2018 3:31 pm
Cornflakes_91 wrote:
Thu Feb 15, 2018 2:50 pm
what happens when theres heavy lag (for whatever reason), does it still catch all inputs and behave like expected or does it start dropping inputs?
With the callback (which we'll be shipping on) and event loop usage patterns input is never dropped, no matter the framerate.

For direct queries, although we record every button transition (released to pressed or pressed to released) which means we catch _almost_ all input, you can't get the _number_ of presses/releases in a frame from the API. So if a button is pressed twice in the same frame the second press won't be handled. In theory we could make this possible very easily (just return an int from Input_GetPressed instead of a bool) but there's not a good justification for the complexity. We won't be shipping code using this pattern; it's only there to make development easier and it would result in _really_ ugly code like

Code: Select all

for i = 1, Input.GetPressed(Button.Space) do
 -- Do stuff...
end
Also direct queries don't preserve the order of events, so it's by definition not going to be a robust pattern.
just trying to make sure that that thought isnt missed with the input overhaul :)
enough games' input just dies when the framerate sinks
Post

Re: [Adam] Thursday, February 15, 2018

#8
Do y'all just crash when a script breaks? How 90's!
These days we have fancy try/catch blocks, or a general catchall UnhandledException to make sure the game keeps going and we can see more instances of the bug.

I mean, even Goatbot doesn't entirely crash, unless I add an infinite loop by accident.
But you have access to limit the run length of a single script iteration, and cancel it if it runs too long, so you can avoid that one problem.
°˖◝(ಠ‸ಠ)◜˖°
Toba - A Development Dump
Post

Re: [Adam] Thursday, February 15, 2018

#10
Woot!.. thanks for the update Adam.. I was wondering why we hadn't heard from Lindsey (hot water, freshly squeezed lemon, manuka honey and ginger, dearie), (and good to know Josh is still Josh :D )

AdamByrd wrote:
Thu Feb 15, 2018 2:11 pm

General Infrastructure
I think I finally learned my lesson this time: Command Prompt / Batch files suck. I'm done with them for good this time. I'm going to pick a new language for tools.
As a person who loves DOS scripting (I can write scripts with greater ease than I can with the likes of Powershell), what do you find most limiting about DOS batch files?
(Yes, I call it DOS, and do understand it's no longer the command.com of old, but a DOS-emulator of sorts utlised by cmd.exe :lol: )

Would you consider Powershell, Python or something else?
From the sounds of it, the difficulty is with porting from linux to windows?
confused.com

:ghost:
YAY PYTHON \o/

In Josh We Trust
-=326.3827=-
Post

Re: [Adam] Thursday, February 15, 2018

#12
Silverware wrote:
Thu Feb 15, 2018 5:08 pm
These days we have fancy try/catch blocks, or a general catchall UnhandledException to make sure the game keeps going and we can see more instances of the bug.
Josh and I are both of the opinion that that's a one way ticket to crazy town. Exceptions have failed miserably. And continuing to run a program in an unknown state is a poor solution.

You write bad code you get a SIGABRT and an opportunity to re-evaluate your choices.

FormalMoss wrote:
Thu Feb 15, 2018 5:25 pm
what do you find most limiting about DOS batch files?
Pretty much everything. It's painfully obtuse and inconsistent as a 'language'. Basic things take way more work than necessary. Things fail in obscure ways. Take my recent task:
  • I want to add color to warnings and errors in the build output.
  • I wonder if Command Prompt supports ANSI escape sequences?
  • Cool, it does. Oh wait, it's only in an update to Windows 10. Well I hope no developers join that want to use an older version.
  • Ok, let's capture the compiler output in a variable so we can loop over the lines...
  • Bunch of Google searching...
  • There's no way to do that. Wat.
  • Looks like I can loop over the output with for: for /F %%F in ("%CC% %SRCPATH% %LN%")
  • Hey, we've got color!
  • Shit, the for loop isn't parsing the output until the compiler finishes completely. Output is no longer realtime and we lose the ability to tell if one file is taking longer than others.
  • Bunch of Google searching...
  • Of course there's no way to parse the output line by line in a for loop that parses text LINE-BY-LINE. ARRG.
  • Bunch more Google searching...
  • Ok, so the only way to process output in realtime is to kick off another process/batch script.
  • I don't want a second batch script just to handle this. I'll add a switch to our build script that parses and returns the modified text. Then I'll pipe the compiler output to it.
  • Back to the first problem: how do I parse the input to the second batch instance line by line?
  • An asston more Google searching...
  • Apparently this really obscure pattern will work: for /F %%F IN ('more')
  • Why on earth does more work here? You know what, I don't care. I've wasted hours on this and it was supposed to be a quick fun thing to boost morale. At least it works.
  • @#$%^&*! NOW I CAN'T GET THE ERROR CODE BACK FROM THE COMPILER SO I DON'T KNOW IF IT SUCCEEDED FOR FAILED.
  • Returning an exit code from the second batch instance simply doesn't work. You know, THE THING EXIT CODES ARE MADE FOR WHO WROTE THIS DUMPSTER FIRE OF A LANGUAGE
  • Google can't even help me now. I try random things until my fingers and eyes bleed.
  • I discover something that for no discernible reason works most of the time. But sometimes, just sometimes, it thinks the build succeeded when it failed.
  • FINE. It says build succeeded sometimes when it doesn't. That's not going to drive my OCD up the wall everyday. At least that doesn't break anything. It mostly wo...
  • Oh great, sometimes the compiler output lines get their order shuffled and the output coloring screws up a few lines. WTH? This looks like a concurrency bug but it should be synchrono....SCREW IT. IT'S FINE. I'M GOING HOME.
  • Next morning...
  • Hey, let's profile the build while we are in here and see if we can speed it up a little...
  • DEAR GOD, vcvarsall.bat is taking longer than the actual compile.
  • Screw it. Screw Microsoft. Screw batch. Screw everything. I give up.

FormalMoss wrote:
Thu Feb 15, 2018 5:25 pm
Would you consider Powershell, Python or something else?
Something about Powershell rubs me the wrong way. Admittedly, I haven't used it heavily, but I at this point I don't want to rely on another OS specific feature anyway. Python is an option. Lua too, maybe. I also need an excuse to learn some Rust and Go (not sure if either are particularly good choices for tools, but it'd be fun to find out). As tired of it as I am, C# is solid for tools too.
Post

Re: [Adam] Thursday, February 15, 2018

#13
AdamByrd wrote:
Thu Feb 15, 2018 7:15 pm
Silverware wrote:
Thu Feb 15, 2018 5:08 pm
These days we have fancy try/catch blocks, or a general catchall UnhandledException to make sure the game keeps going and we can see more instances of the bug.
Josh and I are both of the opinion that that's a one way ticket to crazy town. Exceptions have failed miserably. And continuing to run a program in an unknown state is a poor solution.

You write bad code you get a SIGABRT and an opportunity to re-evaluate your choices.
Fair enough, although it'd be nice if it were togglable for debugging purposes when modding.
Because when I start on multiplayer I can guarantee you that a missed packet is going to cause crashes, and that's going to get super frustrating super fast.
°˖◝(ಠ‸ಠ)◜˖°
Toba - A Development Dump
Post

Re: [Adam] Thursday, February 15, 2018

#14
Silverware wrote:
Thu Feb 15, 2018 7:22 pm
Because when I start on multiplayer I can guarantee you that a missed packet is going to cause crashes, and that's going to get super frustrating super fast.
We wouldn't intentionally crash the engine in that case, so I'm assuming you mean the net result of ending up with bad data due to a missed packet is a crash somewhere down the line? If so, I understand wanting some leeway there. The signal handling is actually exposed as an engine API, so you can register your own signal handlers and flag the signal to be ignored if you want.

That said, we mostly crash on irrecoverable errors like trying to do a raycast against a BSP when a null pointer was passed. Or, more commonly trying to use a value in Lua that doesn't exist. Even ignoring the signal isn't going to get you passed that point.
Post

Re: [Adam] Thursday, February 15, 2018

#15
AdamByrd wrote:
Thu Feb 15, 2018 7:54 pm
Silverware wrote:
Thu Feb 15, 2018 7:22 pm
Because when I start on multiplayer I can guarantee you that a missed packet is going to cause crashes, and that's going to get super frustrating super fast.
We wouldn't intentionally crash the engine in that case, so I'm assuming you mean the net result of ending up with bad data due to a missed packet is a crash somewhere down the line? If so, I understand wanting some leeway there. The signal handling is actually exposed as an engine API, so you can register your own signal handlers and flag the signal to be ignored if you want.

That said, we mostly crash on irrecoverable errors like trying to do a raycast against a BSP when a null pointer was passed. Or, more commonly trying to use a value in Lua that doesn't exist. Even ignoring the signal isn't going to get you passed that point.
Ah right okay, that makes more sense then, I was thinking for all exceptions caused by scripts. :V
Which would quickly fail the moment something happened (or didn't happen) unexpectedly. (eg: all the time when dealing with networks :V)
°˖◝(ಠ‸ಠ)◜˖°
Toba - A Development Dump

Online Now

Users browsing this forum: No registered users and 0 guests

cron