Backlog/engine/core/src/inputs/inputStack.zig

754 lines
23 KiB
Zig

pub const BindingType = enum(u8) {
action,
axis1d,
axis2d,
};
const glfw_release = 0;
pub const IOEvent = union(enum(u8)) {
windowFocused: struct { focused: c_int },
mousePosition: struct { x: f64, y: f64 },
scroll: struct { xoffset: f64, yoffset: f64 },
key: struct { key: Key, scancode: Key, action: ActionEvent, mods: c_int },
mouseRelative: core.Vector2f,
windowResize: struct { newSize: core.Vector2f },
codepoint: c_uint,
};
pub const ActionEvent = enum(u8) {
keyUp,
keyDown,
keyHeld,
};
pub const ActionBindingKey = struct {
key: Key,
event: ActionEvent = .keyDown,
};
fn arenaAlloc() std.mem.Allocator {
return getInputStack().arena.allocator();
}
fn BindingData(Func: type) type {
return struct {
pub const Delegate = core.EngineObjectDelegate(Func);
keysDown: KeysDownQueue,
keysUp: std.ArrayListUnmanaged(Key) = .{},
listeners: std.ArrayListUnmanaged(Delegate) = .{},
listenerId: std.ArrayListUnmanaged(u32) = .{},
consumed: bool = false,
routed: bool = false,
count: u32 = 0,
name: core.Name,
pub const KeysDownQueue = core.RingQueueU(struct { key: Key, first: bool = true });
pub fn init(name: core.Name) @This() {
return .{
.name = name,
.keysDown = KeysDownQueue.init(arenaAlloc(), 256) catch unreachable,
};
}
// pub fn addListener(self: *@This(), ctx: ?*anyopaque, func: Func) u32 {
// const allocator = arenaAlloc();
// self.listeners.append(allocator, .{ .id = self.count, .ctx = ctx, .func = func }) catch unreachable;
// self.count +%= 1;
// return self.count;
// }
//pub fn addListener(self: *@This(), ctx:?*anyopaque, func: Func)
pub fn addListener(self: *@This(), comptime T: type, comptime funcName: []const u8, target: *anyopaque) u32 {
const delegate = Delegate{
.target = target,
.func = @field(T, funcName),
.version = T.NeonObjectTable.version,
.functionName = funcName,
.vtable = &T.NeonObjectTable,
};
const allocator = arenaAlloc();
self.listeners.append(allocator, delegate) catch unreachable;
self.listenerId.append(allocator, self.count) catch unreachable;
self.count +%= 1;
return self.count;
}
pub fn removeListener(self: *@This(), id: u32) void {
for (self.listeners.items.len, 0..) |listener, i| {
if (listener.id == id) {
_ = self.listenerId.swapRemove(i);
_ = self.listeners.swapRemove(i);
return;
}
}
}
pub fn setKeyDown(self: *@This(), key: Key) void {
self.keysDown.push(.{ .key = key, .first = true }) catch unreachable;
}
pub fn setKeyHeld(self: *@This(), key: Key) void {
self.keysDown.push(.{ .key = key, .first = false }) catch unreachable;
}
pub fn setKeyUp(self: *@This(), key: Key) void {
self.keysUp.append(arenaAlloc(), key) catch unreachable;
}
pub fn deinit(self: *@This()) void {
self.keysDown.deinit(arenaAlloc());
self.listeners.deinit(arenaAlloc());
}
};
}
pub const ActionBinding = struct {
data: BindingData(ActionFunc),
keys: std.ArrayListUnmanaged(ActionBindingKey) = .{},
layer: ?*BindingLayer = null,
// pub const Listener = struct {
// id: u32,
// delegate: core.
// };
pub fn create(name: core.Name) !*@This() {
const allocator = arenaAlloc();
const self = try allocator.create(@This());
self.* = .{
.data = BindingData(ActionFunc).init(name),
};
return self;
}
pub fn addToLayer(self: *@This(), layer: *BindingLayer) !void {
try layer.addBindingByName(self.data.name, self);
}
pub fn removeFromLayer(self: *@This(), layer: *BindingLayer) !void {
try layer.removeBindingByName(self.data.name);
}
// pushes this binding to the active layer
pub fn activate(self: *@This()) void {
if (getInputStack().active) |active| {
active.addBindingByName(self.data.name, self) catch unreachable;
}
}
pub fn deactivate(self: *@This()) void {
if (getInputStack().active) |active| {
active.removeBindingByName(self.data.name);
}
}
pub fn addKey(self: *@This(), key: Key, event: ActionEvent) void {
const allocator = arenaAlloc();
self.keys.append(allocator, .{ .key = key, .event = event }) catch unreachable;
}
pub fn deinit(self: *@This()) void {
const allocator = arenaAlloc();
self.data.deinit();
self.keys.deinit(allocator);
}
};
pub const ActionFunc = *const fn (?*anyopaque, ActionEvent) void;
pub const AxisBindingKeyMagnitude = struct {
key: Key,
magnitude: f32 = 1.0,
};
pub const Axis1dFunc = *const fn (?*anyopaque, f32) void;
pub const Axis1dBinding = struct {
data: BindingData(Axis1dFunc),
keys: std.ArrayListUnmanaged(AxisBindingKeyMagnitude) = .{},
magnitude: f32 = 0.0,
clampValue: ?f32 = 1.0,
// pub const Listener = struct {
// id: u32,
// ctx: ?*anyopaque,
// func: Axis1dFunc,
// };
pub fn create(name: core.Name) !*@This() {
const allocator = arenaAlloc();
const self = try allocator.create(@This());
self.* = .{
.data = BindingData(Axis1dFunc).init(name),
};
return self;
}
pub fn addToLayer(self: *@This(), layer: *BindingLayer) !void {
try layer.addBindingByName(self.data.name, self);
}
pub fn removeFromLayer(self: *@This(), layer: *BindingLayer) !void {
try layer.removeBindingByName(self.data.name);
}
// pushes this binding to the active layer
pub fn activate(self: *@This()) void {
if (getInputStack().active) |active| {
active.addBindingByName(self.data.name, self) catch unreachable;
}
}
pub fn setClamp(self: *@This(), clampValue: ?f32) void {
self.clampValue = clampValue;
}
pub fn deactivate(self: *@This()) void {
if (getInputStack().active) |active| {
active.removeBindingByName(self.data.name);
}
}
pub fn addKey(self: *@This(), key: Key, magnitude: f32) void {
const allocator = arenaAlloc();
self.keys.append(allocator, .{ .key = key, .magnitude = magnitude }) catch unreachable;
}
pub fn deinit(self: *@This()) void {
const allocator = arenaAlloc();
self.data.deinit();
self.keys.deinit(allocator);
self.listeners.deinit(allocator);
}
};
pub const Axis2dFunc = *const fn (?*anyopaque, core.Vector2f) void;
pub const Axis2dBinding = struct {
data: BindingData(Axis2dFunc),
yKeys: std.ArrayListUnmanaged(AxisBindingKeyMagnitude) = .{},
xKeys: std.ArrayListUnmanaged(AxisBindingKeyMagnitude) = .{},
// if mouseRelative is set to true then relative mouse movement values shall be passed to the listener
//
mouseRelative: bool = false,
magnitude: core.Vector2f = core.Vector2f.Zeroes,
clampValue: ?f32 = 1.0,
// pub const Listener = struct {
// id: u32,
// ctx: ?*anyopaque,
// func: Axis2dFunc,
// };
pub fn addToLayer(self: *@This(), layer: *BindingLayer) !void {
try layer.addBindingByName(self.data.name, self);
}
pub fn removeFromLayer(self: *@This(), layer: *BindingLayer) !void {
try layer.removeBindingByName(self.data.name);
}
pub fn create(name: core.Name) !*@This() {
const allocator = arenaAlloc();
const self = try allocator.create(@This());
self.* = .{
.data = BindingData(Axis2dFunc).init(name),
};
return self;
}
pub fn disableMouseRelative(self: *@This()) void {
self.mouseRelative = false;
}
pub fn enableMouseRelative(self: *@This()) void {
self.mouseRelative = true;
self.clampValue = null;
}
pub fn setClamp(self: *@This(), clampValue: ?f32) void {
self.clampValue = clampValue;
}
// pushes this binding to the active layer
pub fn activate(self: *@This()) void {
if (getInputStack().active) |active| {
active.addBindingByName(self.data.name, self) catch unreachable;
}
}
pub fn deactivate(self: *@This()) void {
if (getInputStack().active) |active| {
active.removeBindingByName(self.data.name);
}
}
pub fn addKey(self: *@This(), key: Key, magnitude: f32, axis: enum { x, y }) void {
const allocator = arenaAlloc();
switch (axis) {
.x => {
self.xKeys.append(allocator, .{ .key = key, .magnitude = magnitude }) catch unreachable;
},
.y => {
self.yKeys.append(allocator, .{ .key = key, .magnitude = magnitude }) catch unreachable;
},
}
}
pub fn deinit(self: *@This()) void {
const allocator = arenaAlloc();
self.xKeys.deinit(allocator);
self.yKeys.deinit(allocator);
self.data.deinit();
}
};
pub const Binding = union(BindingType) {
action: *ActionBinding,
axis1d: *Axis1dBinding,
axis2d: *Axis2dBinding,
pub fn setKeyDown(self: @This(), key: Key) void {
switch (self) {
.action => |p| {
p.data.setKeyDown(key);
},
.axis1d => |p| {
p.data.setKeyDown(key);
},
.axis2d => |p| {
p.data.setKeyDown(key);
},
}
}
pub fn setKeyHeld(self: @This(), key: Key) void {
switch (self) {
.action => |p| {
p.data.setKeyHeld(key);
},
.axis1d => |p| {
p.data.setKeyHeld(key);
},
.axis2d => |p| {
p.data.setKeyHeld(key);
},
}
}
pub fn setKeyUp(self: @This(), key: Key) void {
switch (self) {
.action => |p| {
p.data.setKeyUp(key);
},
.axis1d => |p| {
p.data.setKeyUp(key);
},
.axis2d => |p| {
p.data.setKeyUp(key);
},
}
}
pub fn sendAxisUpdates(self: @This()) void {
switch (self) {
.action => {},
.axis1d => |p| {
p.data.routed = false;
for (p.data.listeners.items) |*listener| {
listener.call(p.magnitude);
}
p.magnitude = 0.0;
},
.axis2d => |p| {
p.data.routed = false;
for (p.data.listeners.items) |*listener| {
listener.call(p.magnitude);
}
p.magnitude = core.Vector2f.Zeroes;
},
}
}
pub fn routeBindingEvent(self: @This(), key: Key, event: ActionEvent, consumedOut: *bool) bool {
var consumed: bool = false;
var routed: bool = false;
switch (self) {
.action => |p| {
for (p.keys.items) |keyBinding| {
if (keyBinding.key == key and event == keyBinding.event) {
consumed = p.data.consumed;
routed = true;
}
}
if (routed) {
for (p.data.listeners.items) |*listener| {
listener.call(event);
}
}
p.data.routed = routed;
},
.axis1d => |p| {
for (p.keys.items) |keyBinding| {
if (keyBinding.key == key and (event == .keyDown or event == .keyHeld)) {
p.magnitude += keyBinding.magnitude;
consumed = p.data.consumed;
routed = true;
}
}
if (p.clampValue) |clampValue| {
p.magnitude = std.math.clamp(p.magnitude, -clampValue, clampValue);
}
p.data.routed = routed;
},
.axis2d => |p| {
for (p.xKeys.items) |keyBinding| {
if (keyBinding.key == key and (event == .keyDown or event == .keyHeld)) {
p.magnitude.x += keyBinding.magnitude;
consumed = p.data.consumed;
routed = true;
}
}
for (p.yKeys.items) |keyBinding| {
if (keyBinding.key == key and (event == .keyDown or event == .keyHeld)) {
p.magnitude.y += keyBinding.magnitude;
consumed = p.data.consumed;
routed = true;
}
}
if (p.clampValue) |clampValue| {
p.magnitude.x = std.math.clamp(p.magnitude.x, -clampValue, clampValue);
p.magnitude.y = std.math.clamp(p.magnitude.y, -clampValue, clampValue);
}
p.data.routed = routed;
},
}
consumedOut.* = consumed;
return routed;
}
fn processKeysHeldDown(self: @This(), p: anytype) void {
const keysCount = p.data.keysDown.count();
var i: usize = 0;
while (i < keysCount) : (i += 1) {
const keydown = p.data.keysDown.pop().?;
var isKeyUp: bool = false;
for (p.data.keysUp.items) |up| {
if (up == keydown.key) {
isKeyUp = true;
break;
}
}
var consumed: bool = false;
if (!isKeyUp and !keydown.first) {
_ = self.routeBindingEvent(keydown.key, .keyHeld, &consumed);
}
if (!isKeyUp) {
p.data.setKeyHeld(keydown.key);
}
}
p.data.keysUp.clearRetainingCapacity();
}
pub fn processHeldKeys(self: @This()) void {
switch (self) {
.action => |p| {
self.processKeysHeldDown(p);
},
.axis1d => |p| {
self.processKeysHeldDown(p);
},
.axis2d => |p| {
self.processKeysHeldDown(p);
},
}
}
pub fn releaseKeys(self: @This()) void {
switch (self) {
.action => |p| {
p.keysDown.clearRetainingCapacity();
},
.axis1d => |p| {
p.keysDown.clearRetainingCapacity();
},
.axis2d => |p| {
p.keysDown.clearRetainingCapacity();
},
}
}
};
pub const BindingLayer = struct {
allocator: std.mem.Allocator,
bindingsByName: std.AutoHashMapUnmanaged(u32, u32) = .{},
bindingStack: std.ArrayListUnmanaged(Binding) = .{},
pub fn create(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{
.allocator = allocator,
};
return self;
}
pub fn addBindingByName(self: *@This(), _name: core.Name, new: anytype) !void {
var name = _name;
if (self.bindingsByName.contains(name.handle())) {
self.removeBindingByName(name);
}
try self.bindingsByName.put(self.allocator, name.handle(), @intCast(self.bindingStack.items.len));
var newBinding: Binding = undefined;
switch (@TypeOf(new)) {
*ActionBinding => {
newBinding = .{ .action = new };
},
*Axis1dBinding => {
newBinding = .{ .axis1d = new };
},
*Axis2dBinding => {
newBinding = .{ .axis2d = new };
},
else => {
@panic("uh oh");
},
}
try self.bindingStack.append(self.allocator, newBinding);
}
pub fn removeBindingByName(self: *@This(), _name: core.Name) void {
var name = _name;
const index = self.bindingsByName.get(name.handle()).?;
_ = self.bindingStack.orderedRemove(index);
_ = self.bindingsByName.remove(name.handle());
var iter = self.bindingsByName.valueIterator();
while (iter.next()) |next| {
if (next.* > index) {
next.* -= 1;
}
}
}
pub fn destroy(self: *@This()) void {
self.bindingsByName.deinit(self.allocator);
self.bindingStack.deinit(self.allocator);
self.allocator.destroy(self);
}
};
// not gonna actually deal with layers right now
pub const InputStack = struct {
allocator: std.mem.Allocator,
active: ?*BindingLayer,
bindingStack: std.ArrayListUnmanaged(*BindingLayer) = .{},
arena: std.heap.ArenaAllocator,
keysDown: std.AutoHashMapUnmanaged(Key, bool) = .{},
mousePosition: core.Vector2f = .{},
pub fn setMousePosition(self: *@This(), x: f32, y: f32) void {
self.mousePosition.x = x;
self.mousePosition.y = y;
}
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "core.InputStack");
pub fn init(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{
.allocator = allocator,
.arena = std.heap.ArenaAllocator.init(allocator),
.active = try BindingLayer.create(allocator),
};
core.EngineObject(@This()).gInstance = self;
return self;
}
pub fn updatePreviousInputs(self: *@This()) void {
if (self.active == null) {
return;
}
const active = self.active.?;
if (active.bindingStack.items.len == 0) {
return;
}
var i: i32 = @intCast(active.bindingStack.items.len - 1);
while (i >= 0) : (i -= 1) {
const binding = &active.bindingStack.items[@intCast(i)];
binding.processHeldKeys();
}
}
pub fn sendAxisUpdates(self: *@This()) void {
if (self.active == null) {
return;
}
const active = self.active.?;
for (active.bindingStack.items) |binding| {
binding.sendAxisUpdates();
}
}
fn routeKeyEvent(self: *@This(), key: Key, event: ActionEvent) void {
if (self.active == null) {
return;
}
const active = self.active.?;
// for all bindings in reverse order from this layer, route the input, stop the
// route if the input was consumed
// core.engine_log(" >> {any} > routeKeyEvent 1", .{key});
if (active.bindingStack.items.len == 0) {
return;
}
// core.engine_log(" >> {any} > routeKeyEvent 2", .{key});
var i: i32 = @intCast(active.bindingStack.items.len - 1);
// core.engine_log(" >> {any} > routeKeyEvent 2 binding count : {d}", .{ key, i });
while (i >= 0) : (i -= 1) {
const binding = active.bindingStack.items[@intCast(i)];
var consumed: bool = false;
var routed: bool = false;
if (binding.routeBindingEvent(key, event, &consumed)) {
routed = true;
}
if (event == .keyDown) {
binding.setKeyDown(key);
} else if (event == .keyHeld) {
binding.setKeyHeld(key);
} else if (event == .keyUp) {
binding.setKeyUp(key);
}
if (consumed) {
break;
}
}
}
fn routeMouseRelative(self: *@This(), move: core.Vector2f) void {
if (self.active == null) {
return;
}
const active = self.active.?;
if (active.bindingStack.items.len == 0) {
return;
}
var i: i32 = @intCast(active.bindingStack.items.len - 1);
// core.engine_log(" >> {any} > routeKeyEvent 2 binding count : {d}", .{ key, i });
while (i >= 0) : (i -= 1) {
const binding = active.bindingStack.items[@intCast(i)];
switch (binding) {
// only axis2d handles mouse relative right now,
.axis2d => |axis| {
if (axis.mouseRelative) {
axis.magnitude = axis.magnitude.add(move);
}
},
else => {},
}
}
}
pub fn routeEvent(self: *@This(), eventToRoute: IOEvent) void {
switch (eventToRoute) {
.key => |key| {
const keyEvent: ActionEvent = key.action; // @enumFromInt(@as(u8, @intCast(key.action)));
if (keyEvent != .keyHeld) {
// core.engine_log("converted: {any}", .{keyEvent});
self.routeKeyEvent(key.key, keyEvent); // do not route keyHelds
}
},
.mouseRelative => |rel| {
self.routeMouseRelative(rel);
},
else => {},
}
}
pub fn deinit(self: *@This()) void {
self.arena.deinit();
if (self.active) |active| {
active.destroy();
}
self.allocator.destroy(self);
}
};
pub fn getInputStack() *InputStack {
return core.EngineObject(InputStack).get();
}
pub fn initInputStack() !void {
_ = try core.createObject(InputStack, .{});
}
// creates a binding for a lua type
// TODO
// pub fn LuaBinding(comptime T: type, comptime typeName: []const u8) type {
// return struct {
// p: ?*T,
//
// pub const PodDataTable = pod.DataTable{
// .name = typeName,
// .funcs = &.{},
// };
//
// pub fn create() @This() {
// return .{};
// }
// };
// }
const lua = core.lua;
const pod = lua.pod;
pub const Key = @import("keys.zig").Key;
const std = @import("std");
const core = @import("../core.zig");