Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Game controller support #717

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open

Conversation

catmeow72
Copy link
Contributor

@catmeow72 catmeow72 commented Sep 12, 2024

This pull request adds controller support to the game.
TODO:

  • Add controller input
  • Read controller mappings
    • Controller mappings from file in binary directory
    • Controller mappings from environment variable
    • Controller mappings from config
  • Download controller mappings
  • Add cursor sprite for controller cursor

Allow saving and configuring gamepad mappings
Add gamepad sensitivity, for camera only
Adjust multipliers in usage of gamepad axes
Prompt for downloading controller mappings, and allow configuration
… duplicate code, or have to detect if startup has finished
@catmeow72 catmeow72 marked this pull request as ready for review September 13, 2024 16:52
Copy link
Member

@IntegratedQuantum IntegratedQuantum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't looked through the entire code yet, but about half-way through I discovered that I keep repeating myself, so please apply my comments to the remaining code.

I mainly highlighted some syntax changes to simplify the code or make it less error prone.
Most notably, please use defer.

Also I would suggest to use the stackAllocator for all local allocations. It's much faster than using the global allocator.

And finally: Only OutOfMemory errors are unreachable. Other errors, like for file and network code can be reached by e.g. missing network connection or incorrect file permission. These errors must be handled and printed, otherwise we end up in hard to debug situations where the game just crashes and we have no idea why.

src/graphics/Window.zig Outdated Show resolved Hide resolved
src/graphics/Window.zig Outdated Show resolved Hide resolved
src/graphics/Window.zig Outdated Show resolved Hide resolved
src/graphics/Window.zig Outdated Show resolved Hide resolved
src/graphics/Window.zig Outdated Show resolved Hide resolved
src/graphics/Window.zig Outdated Show resolved Hide resolved
src/graphics/Window.zig Outdated Show resolved Hide resolved
src/graphics/Window.zig Outdated Show resolved Hide resolved
src/graphics/Window.zig Outdated Show resolved Hide resolved
src/graphics/Window.zig Outdated Show resolved Hide resolved
Copy link
Member

@IntegratedQuantum IntegratedQuantum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for taking so long to review.
I still think you could do some improvements here.
Could also explain the intentions behind this new cursor you added?

const Gamepad = struct {
gamepadState: std.AutoHashMap(c_int, *c.GLFWgamepadstate) = undefined,
controllerMappingsDownloaded: bool = false,
controllerMappingsTask: ?ControllerMappingDownloadTask = null,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this has only one instance, you might as well just make these public variables.

self.gamepadState.put(jid, main.globalAllocator.create(c.GLFWgamepadstate)) catch unreachable;
}
_ = c.glfwGetGamepadState(jid, self.gamepadState.get(jid).?);
const oldState: c.GLFWgamepadstate = oldGamepadState orelse std.mem.zeroes(c.GLFWgamepadstate);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you even have these twice, like you can just store the std.mem.zeroes(c.GLFWgamepadstate) in oldGamepadState and not make it optional.

key.value = newAxis;
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it's almost the exact same code as 65-121
Is it possible to combine these into one?
It seems to be more error prone the way it is right now. For example you forgot to apply the deadzone in this version.

}
if(GLFWCallbacks.currentPos[1] >= winSize[1]) {
GLFWCallbacks.currentPos[1] = winSize[1] - 1;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to write it this verbose. What you are doing here is just a clamp on a vector. You can use std.math.clamp for that.

}
const ControllerMappingDownloadTask = struct { // MARK: ControllerMappingDownloadTask
curTimestamp: i128,
controllerMappingsDownloaded: *bool,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This construct seems a bit weird.
Instead of scheduling multiple tasks and removing the duplicate ones after the first one finished, you should just not schedule multiple tasks in the first place. This should be simpler to implement too.

(also for atomics it would be better ot use std.atomic.Value, to make this more type safe)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already had it so that it doesn't schedule multiple tasks, but in a different function. I'll add this to the schedule function as well.

if (files.openDir(".") catch null) |dir| {
var _dir: files.Dir =dir;
if (_dir.hasFile("gamecontrollerdb.stamp")) {
const stamp = _dir.read(main.globalAllocator, "gamecontrollerdb.stamp") catch unreachable;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error is not unreachable! The file might for example have incorrect permissions.
also: stackAllocator

timestamp = newTimestamp;
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here a block would be more readable in my opinion:

const timestamp: i128 = blk: {
	var dir = files.openDir(".") catch break :blk 0;
	if(!dir.hasFile(...)) break :blk 0;
	const stamp = _dir.read(main.stackAllocator, "gamecontrollerdb.stamp") catch break :blk 0;
	defer main.stackAllocator.free(stamp);
	break :blk std.fmt.parseInt(i128, stamp, 16) catch 0;
};

pub fn init() *Gamepad {
const self: *Gamepad = main.globalAllocator.create(Gamepad);
if (!settings.askToDownloadControllerMappings) {
self.*.downloadControllerMappings();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to do .* when accessing members of a pointer.
This is Zig, not C/C++.

self.*.downloadControllerMappings();
}
self.*.updateControllerMappings();
self.*.gamepadState = std.AutoHashMap(c_int, *c.GLFWgamepadstate).init(main.globalAllocator.allocator);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As of the new zig version that I updated to recently, you can just use

self.*.gamepadState = .init(main.globalAllocator.allocator);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants