Testing gamepad input in the in-game prompt.
Work Since Last Devlog
More hacking away at the input system. I found some helpful joystick-related callbacks on the LÖVE wiki (scroll down to the Joystick section) that I either missed or didn’t understand while implementing gamepad support in Hibernator.
I now have keybinds stored in a configuration file/model which can be applied to LÖVE keyConstants, ScanCodes, gamepad input and (some) mouse actions. By default, I have it set up so that the player moves when pressing arrow keys, WASD, the d-pad, or the left thumbstick on any connected gamepad, and it can all be reconfigured by an end-user without digging through the source code. I’m very happy about this.
LÖVE provides a lower-level joystick interface and a slightly higher-level gamepad mapping which treats certain controllers like an XBox 360 gamepad. I think that for now, I will just support the gamepad construct, and leave the joystick polling for another day. I noticed that reading the axis value for the analog shoulder triggers yields different results when read as a joystick or as a LÖVE gamepad, so things like that would have to be taken into account. I’m also going to just lump together the input of all gamepads at once.
I wasn’t planning on supporting force-feedback vibration, but LÖVE’s callbacks make it a piece of cake, so… why not. The only issue is that I haven’t really played a lot of games with force feedback, so I’m not sure what the norms are. I added a wrapper so that only the last-polled gamepad gets feedback signals.
I added an ignore list for controllers based on their GUID. Maybe someone out there could find it useful. Shrug.
Earlier, I made config-get functions to set up modules, but I neglected to write the matching config-push functions to save their current state. I made them now, and also wrote some test commands in the backtick console to do the saving and file-writing.
I made a big mistake somewhere in the config parser, which took a couple of hours to track down. I’ll need to go through the error handling for the config system, and ensure that it clearly communicates to the end user (and me!) what particular chunk of what configuration file it had trouble with.
I’ve been slowly going through every configuration item to ensure that:
- It’s in the engine
- It’s linked to a variable in a module
- The linked variable actually does what it’s supposed to do
- The value is saving to disk correctly
While it’s been a slog, it’s also helped paint a picture of of where I’m at. I’m not finished with the process, and probably won’t be until I’m close to releasing a game.
Earlier, I tore out the BGM and SFX modules with the intention of replacing them with a unified Audio module that treats all sources the same. After some investigation, I think I’ll have to keep treating sound effects and music separately. I can use the same ADSR / panning properties across both kinds of audio, though.
For static sounds, I added a memory limit based on the total decoded data, but the default limit is higher than what I see myself actually using in a game (128 MB), so I probably shouldn’t bother stress-testing this until I have more content.
Streamed sources seem to have more overhead than I realized. I’ve gone through the following phases based on what I’m seeing in terms of memory usage:
- Just load every piece of BGM as a streamed source up-front at startup.
- ‘1’ uses more memory than I thought it would. Not a problem for a small game, but if a game had like 80 reasonably long Ogg Vorbis tracks, it could rack up hundreds of MB of RAM usage just to hold sources of which only one or two play at a time. So instead: only make one or two sources at a time, and source:release() the old ones to keep memory usage down.
- Either a quirk in LÖVE or a quirk in my codebase is causing released streamed sources to not actually de-allocate, at least from what I’m seeing in Fedora’s System Monitor. So ‘2’ is actually worse than ‘1’, since there’s no apparent upper-bound to how much memory it can consume (I stopped counting at 7GB.) So… it seems like the best way to go about this right now is to make BGM sources on-demand as they need to be played, and just hold onto them in case they need to be played again. (e 3/sept/2019: OK, I think I see what’s going on. Calling collectgarbage(“collect”) immediately after source:release() seems to be freeing memory as expected. If I invoke the GC after multiple newSource() calls, it doesn’t lower the advertised memory usage, but if I then hammer the system with even more newSource() calls, it doesn’t continue its upward ascension for a period of time, either. It must just be holding onto allocated memory in case it’s needed, and an immediate GC invocation somehow prevents that from happening. In light of this, I think some kind of ‘load source -> cache it -> trash longest-unheard if over X bytes loaded’ mechanism should be doable.)
Keybinding, human-readable config files, and gamepad input were all interconnected, and all holding each other back. Though I have no visual indications of progress from this work ( 🙁 ), I think August has been a good month overall. I didn’t get to the roadmap, but two out of three isn’t bad I guess.
Plans For Next Post
- I need to rewrite the BGM module to cache previously loaded-and-played music. Once that’s resolved, I can get to work on managing cross-fades.
- So uh, how about that ‘roadmap’ I’ve been promising for multiple posts.
(e 3/Sept/2019: more stuff on streamed sources.)