mach-gamemode moves back into the main repository
Helps hexops/mach#1165 Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
221364415e
commit
d64d30c7db
3 changed files with 265 additions and 2 deletions
2
.github/ISSUE_TEMPLATE/dev_zig_nomination.md
vendored
2
.github/ISSUE_TEMPLATE/dev_zig_nomination.md
vendored
|
|
@ -45,7 +45,6 @@ You may have been linked to this issue because you sent a pull request to update
|
|||
|
||||
These projects have zero `build.zig.zon` dependencies, we update them first - and in any order.
|
||||
|
||||
* [ ] mach-gamemode
|
||||
* [ ] mach-model3d
|
||||
* [ ] mach-sysjs
|
||||
* [ ] mach-objc-generator
|
||||
|
|
@ -133,7 +132,6 @@ These projects have dependencies on other projects. We update them in the exact
|
|||
* [ ] mach-core, which depends on:
|
||||
* build.zig version check
|
||||
* mach-core-example-assets
|
||||
* mach-gamemode
|
||||
* mach-sysgpu
|
||||
* mach-gpu
|
||||
* mach-glfw
|
||||
|
|
|
|||
263
src/gamemode.zig
Normal file
263
src/gamemode.zig
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const log = std.log.scoped(.gamemode);
|
||||
|
||||
test {
|
||||
std.testing.refAllDeclsRecursive(@This());
|
||||
}
|
||||
|
||||
pub const LoadError = error{MissingSymbol} || std.DynLib.Error;
|
||||
pub const Error = error{ RequestFailed, RequestRejected };
|
||||
|
||||
pub const Status = enum(u8) {
|
||||
inactive = 0,
|
||||
active = 1,
|
||||
active_and_registered = 2,
|
||||
|
||||
pub fn isActive(self: Status) bool {
|
||||
const lut = [_]bool{ false, true, true };
|
||||
return lut[@intFromEnum(self)];
|
||||
}
|
||||
};
|
||||
|
||||
pub const advanced = switch (builtin.os.tag) {
|
||||
.linux => linux_impl,
|
||||
else => noop_impl,
|
||||
};
|
||||
|
||||
test "start and end gamemode" {
|
||||
try std.testing.expectEqual(false, isActive());
|
||||
start();
|
||||
stop();
|
||||
try std.testing.expectEqual(false, isActive());
|
||||
}
|
||||
|
||||
pub fn start() void {
|
||||
if (!advanced.init()) return;
|
||||
advanced.requestStart() catch |err| {
|
||||
log.warn("gamemode: failed to start: {}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isActive() bool {
|
||||
const status = advanced.queryStatus() catch |err| {
|
||||
log.warn("gamemode: error querying status: {}", .{err});
|
||||
return false;
|
||||
};
|
||||
return status.isActive();
|
||||
}
|
||||
|
||||
pub fn stop() void {
|
||||
advanced.requestEnd() catch |err| {
|
||||
log.warn("gamemode: failed to end: {}", .{err});
|
||||
};
|
||||
advanced.deinit();
|
||||
}
|
||||
|
||||
const linux_impl = struct {
|
||||
comptime {
|
||||
if (!builtin.link_libc) {
|
||||
// TODO: LibC is currently required for using `dlopen`.
|
||||
// We might get it working using Zig's `ElfDynLib` later, but this would require
|
||||
// finding the location of the gamemode lib somehow. It also wouldn't be 100% correct,
|
||||
// as `LD_PRELOAD` wouldn't be respected.
|
||||
@compileError("Must link LibC for gamemode!");
|
||||
}
|
||||
}
|
||||
|
||||
/// Global state for the library handle
|
||||
var state: State = .uninit;
|
||||
|
||||
const State = union(enum) {
|
||||
uninit,
|
||||
failed,
|
||||
init: GamemodeHandle,
|
||||
};
|
||||
|
||||
const GamemodeHandle = struct {
|
||||
lib: std.DynLib,
|
||||
syms: SymbolTable,
|
||||
};
|
||||
|
||||
const SymbolTable = struct {
|
||||
real_gamemode_error_string: *const fn () callconv(.C) [*:0]const u8,
|
||||
|
||||
real_gamemode_request_start: *const fn () callconv(.C) c_int,
|
||||
real_gamemode_request_end: *const fn () callconv(.C) c_int,
|
||||
real_gamemode_query_status: *const fn () callconv(.C) c_int,
|
||||
|
||||
real_gamemode_request_start_for: *const fn (std.os.pid_t) callconv(.C) c_int,
|
||||
real_gamemode_request_end_for: *const fn (std.os.pid_t) callconv(.C) c_int,
|
||||
real_gamemode_query_status_for: *const fn (std.os.pid_t) callconv(.C) c_int,
|
||||
};
|
||||
|
||||
/// Try to load libgamemode, returning an error when loading fails.
|
||||
/// If you want to ignore errors, call `init` instead.
|
||||
///
|
||||
/// Unlike `init`, calling `init` or `tryInit` after a failure
|
||||
/// will attempt to load libgamemode again.
|
||||
pub fn tryInit() LoadError!void {
|
||||
if (state == .init) return;
|
||||
|
||||
var dl = std.DynLib.openZ("libgamemode.so.0") catch |e| switch (e) {
|
||||
// backwards-compatibility for old gamemode versions
|
||||
error.FileNotFound => try std.DynLib.openZ("libgamemode.so"),
|
||||
else => return e,
|
||||
};
|
||||
errdefer dl.close();
|
||||
|
||||
// Populate symbol table.
|
||||
var sym_table: SymbolTable = undefined;
|
||||
inline for (@typeInfo(SymbolTable).Struct.fields) |field| {
|
||||
@field(sym_table, field.name) = dl.lookup(field.type, field.name ++ &[_:0]u8{}) orelse {
|
||||
log.err("libgamemode missing symbol '{s}'", .{field.name});
|
||||
return error.MissingSymbol;
|
||||
};
|
||||
}
|
||||
|
||||
state = .{ .init = .{ .lib = dl, .syms = sym_table } };
|
||||
}
|
||||
|
||||
/// Initialize gamemode, logging a possible failure.
|
||||
/// If this fails, no more attempts at loading libgamemode will be made.
|
||||
/// Returns true if gamemode is initialized.
|
||||
pub fn init() bool {
|
||||
switch (state) {
|
||||
.init => return true,
|
||||
.failed => return false,
|
||||
.uninit => {
|
||||
tryInit() catch |e| {
|
||||
if (e != error.FileNotFound) {
|
||||
log.warn("Loading gamemode: '{}'. Disabling libgamemode support.", .{e});
|
||||
}
|
||||
state = .failed;
|
||||
return false;
|
||||
};
|
||||
return true;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Deinitializes gamemode.
|
||||
pub fn deinit() void {
|
||||
switch (state) {
|
||||
.init => |*handle| {
|
||||
handle.lib.close();
|
||||
state = .uninit;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if libgamemode has been initialized, false otherwise.
|
||||
pub fn isInit() bool {
|
||||
return state == .init;
|
||||
}
|
||||
|
||||
/// Query the status of gamemode.
|
||||
/// This does blocking IO!
|
||||
pub fn queryStatus() Error!Status {
|
||||
if (!init()) return .inactive;
|
||||
|
||||
const ret = state.init.syms.real_gamemode_query_status();
|
||||
if (ret < 0)
|
||||
return error.RequestFailed;
|
||||
|
||||
return @as(Status, @enumFromInt(ret));
|
||||
}
|
||||
|
||||
/// Query the status of gamemode for a given PID.
|
||||
/// This does blocking IO!
|
||||
pub fn queryStatusFor(pid: std.os.pid_t) Error!Status {
|
||||
if (!init()) return .inactive;
|
||||
|
||||
const ret = state.init.syms.real_gamemode_query_status_for(pid);
|
||||
return switch (ret) {
|
||||
-1 => error.RequestFailed,
|
||||
-2 => error.RequestRejected,
|
||||
else => @as(Status, @enumFromInt(ret)),
|
||||
};
|
||||
}
|
||||
|
||||
/// Request starting gamemode.
|
||||
/// This does blocking IO!
|
||||
pub fn requestStart() Error!void {
|
||||
if (!init()) return;
|
||||
|
||||
const ret = state.init.syms.real_gamemode_request_start();
|
||||
if (ret < 0)
|
||||
return error.RequestFailed;
|
||||
}
|
||||
|
||||
/// Request starting gamemode for a given PID.
|
||||
/// This does blocking IO!
|
||||
pub fn requestStartFor(pid: std.os.pid_t) Error!void {
|
||||
if (!init()) return;
|
||||
|
||||
const ret = state.init.syms.real_gamemode_request_start_for(pid);
|
||||
return switch (ret) {
|
||||
-1 => error.RequestFailed,
|
||||
-2 => error.RequestRejected,
|
||||
else => {},
|
||||
};
|
||||
}
|
||||
|
||||
/// Request stopping gamemode.
|
||||
/// This does blocking IO!
|
||||
pub fn requestEnd() Error!void {
|
||||
if (!init()) return;
|
||||
|
||||
const ret = state.init.syms.real_gamemode_request_end();
|
||||
if (ret < 0)
|
||||
return error.RequestFailed;
|
||||
}
|
||||
|
||||
/// Request stopping gamemode for a given PID.
|
||||
/// This does blocking IO!
|
||||
pub fn requestEndFor(pid: std.os.pid_t) Error!void {
|
||||
if (!init()) return;
|
||||
|
||||
const ret = state.init.syms.real_gamemode_request_end_for(pid);
|
||||
return switch (ret) {
|
||||
-1 => error.RequestFailed,
|
||||
-2 => error.RequestRejected,
|
||||
else => {},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const noop_impl = struct {
|
||||
pub fn tryInit() LoadError!void {}
|
||||
|
||||
pub fn init() bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn deinit() void {}
|
||||
|
||||
pub fn isInit() bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn queryStatus() Error!Status {
|
||||
return .inactive;
|
||||
}
|
||||
|
||||
pub fn queryStatusFor(pid: std.os.pid_t) Error!Status {
|
||||
_ = pid;
|
||||
return .inactive;
|
||||
}
|
||||
|
||||
pub fn requestStart() Error!void {}
|
||||
|
||||
pub fn requestStartFor(pid: std.os.pid_t) Error!void {
|
||||
_ = pid;
|
||||
}
|
||||
|
||||
pub fn requestEnd() Error!void {}
|
||||
|
||||
pub fn requestEndFor(pid: std.os.pid_t) Error!void {
|
||||
_ = pid;
|
||||
}
|
||||
};
|
||||
|
|
@ -9,6 +9,7 @@ pub const sysaudio = @import("mach-sysaudio");
|
|||
|
||||
// Mach standard library
|
||||
pub const ecs = @import("ecs/main.zig");
|
||||
pub const gamemode = @import("gamemode.zig");
|
||||
pub const gfx = @import("gfx/main.zig");
|
||||
pub const math = @import("math/main.zig");
|
||||
pub const testing = @import("testing.zig");
|
||||
|
|
@ -30,5 +31,6 @@ test {
|
|||
_ = math;
|
||||
_ = testing;
|
||||
std.testing.refAllDeclsRecursive(ecs);
|
||||
std.testing.refAllDeclsRecursive(gamemode);
|
||||
std.testing.refAllDeclsRecursive(math);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue