Compare commits
3 Commits
32f8b66761
...
cb62ac2f63
Author | SHA1 | Date |
---|---|---|
LeRoyce Pearson | cb62ac2f63 | |
LeRoyce Pearson | a6fe88b65e | |
LeRoyce Pearson | 0ab23322ea |
12
build.zig
12
build.zig
|
@ -20,6 +20,16 @@ pub fn build(b: *std.Build) void {
|
|||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const mdns = b.dependency("mdns", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const enet = b.dependency("enet", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "seizer-rummy",
|
||||
.root_source_file = .{ .path = "src/main.zig" },
|
||||
|
@ -27,6 +37,8 @@ pub fn build(b: *std.Build) void {
|
|||
.optimize = optimize,
|
||||
});
|
||||
exe.root_module.addImport("seizer", seizer.module("seizer"));
|
||||
exe.linkLibrary(mdns.artifact("mdns"));
|
||||
exe.linkLibrary(enet.artifact("enet"));
|
||||
b.installArtifact(exe);
|
||||
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
|
|
|
@ -19,6 +19,14 @@
|
|||
.url = "https://github.com/leroycep/seizer/archive/8fdc6335641614c1cd844d1ecd5ad937584a443e.tar.gz",
|
||||
.hash = "1220b6f9d0aba788b55a3e26a34ca9793495fbdb062c463cef1433ff937b96aa77d6",
|
||||
},
|
||||
.mdns = .{
|
||||
.url = "https://github.com/leroycep/mdns/archive/ab55ee5a1e3f72d872521b89fd28a771a1271e2e.tar.gz",
|
||||
.hash = "12209523b5d87d2cb4168950c68e7a8f14b1c74001b6d9a79d35286f3631ef5ea210",
|
||||
},
|
||||
.enet = .{
|
||||
.url = "https://github.com/leroycep/enet/archive/624ca683074acead6f96785d9133e074aae59574.tar.gz",
|
||||
.hash = "1220f45c8055a48c87a17b16adb28531072ebf336756b8847d489076ffb55b08e3f0",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
// This makes *all* files, recursively, included in this package. It is generally
|
||||
|
|
|
@ -19,26 +19,26 @@ pub const Interface = struct {
|
|||
destroy: *const fn (?*anyopaque) void,
|
||||
minimum_size: *const fn (?*anyopaque, RenderResources) [2]f32,
|
||||
render: *const fn (?*anyopaque, *seizer.Canvas, RenderResources, [2]f32, [2]f32) void,
|
||||
get_actions: *const fn (?*anyopaque, *std.ArrayList(Action), RenderResources, [2]f32, [2]f32) Error!void,
|
||||
event: *const fn (?*anyopaque, Event) ?Command,
|
||||
};
|
||||
|
||||
pub const RenderResources = struct {
|
||||
hovered: ?Element.Command,
|
||||
hovered: bool,
|
||||
deck: DeckSprites,
|
||||
font: seizer.Canvas.Font,
|
||||
};
|
||||
|
||||
pub const Action = struct {
|
||||
center: [2]f32,
|
||||
command: Command,
|
||||
pub const Event = union(enum) {
|
||||
up,
|
||||
right,
|
||||
down,
|
||||
left,
|
||||
activate,
|
||||
};
|
||||
|
||||
pub const Command = union(enum) {
|
||||
none,
|
||||
goto_reference: protocol.Reference,
|
||||
callback: struct {
|
||||
pointer: ?*anyopaque,
|
||||
function: *const fn (?*anyopaque) void,
|
||||
},
|
||||
};
|
||||
|
||||
const DeckSprites = assets.DeckSprites;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
allocator: std.mem.Allocator,
|
||||
children: std.ArrayListUnmanaged(Element),
|
||||
hovered: usize,
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator) !*@This() {
|
||||
const this = try allocator.create(@This());
|
||||
|
@ -7,6 +8,7 @@ pub fn create(allocator: std.mem.Allocator) !*@This() {
|
|||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.children = .{},
|
||||
.hovered = 0,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
@ -22,7 +24,7 @@ pub fn element(this: *@This()) Element {
|
|||
.destroy = &element_destroy,
|
||||
.minimum_size = &element_minimum_size,
|
||||
.render = &element_render,
|
||||
.get_actions = &element_get_actions,
|
||||
.event = &element_event,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -76,50 +78,45 @@ pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resou
|
|||
const space_around = empty_space / @as(f32, @floatFromInt(num_spaces));
|
||||
|
||||
var x: f32 = min[0] + @max(space_around, 0);
|
||||
for (this.children.items) |child| {
|
||||
for (this.children.items, 0..) |child, child_index| {
|
||||
const child_size = child.interface.minimum_size(child.pointer, render_resources);
|
||||
|
||||
const child_min = [2]f32{ x, min[1] };
|
||||
const child_max = [2]f32{ x + child_size[0], max[1] };
|
||||
|
||||
child.interface.render(child.pointer, canvas, render_resources, child_min, child_max);
|
||||
var child_render_resources = render_resources;
|
||||
child_render_resources.hovered = render_resources.hovered and child_index == this.hovered;
|
||||
child.interface.render(child.pointer, canvas, child_render_resources, child_min, child_max);
|
||||
|
||||
x += child_size[0] + space_around;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Element.Action), render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
|
||||
pub fn element_activate(pointer: ?*anyopaque) ?Element.Command {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
if (this.children.items.len == 0) return;
|
||||
if (this.hovered >= this.children.items.len) return null;
|
||||
const hovered = this.children.items[this.hovered];
|
||||
return hovered.interface.activate(hovered.pointer);
|
||||
}
|
||||
|
||||
const parent_size = [2]f32{
|
||||
max[0] - min[0],
|
||||
max[1] - min[1],
|
||||
pub fn element_event(pointer: ?*anyopaque, event: Element.Event) ?Element.Command {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
if (this.hovered < this.children.items.len) {
|
||||
const hovered = this.children.items[this.hovered];
|
||||
if (hovered.interface.event(hovered.pointer, event)) |cmd| {
|
||||
return cmd;
|
||||
}
|
||||
}
|
||||
|
||||
const new_hovered: usize = switch (event) {
|
||||
.right => if (this.hovered < this.children.items.len - 1) this.hovered + 1 else return null,
|
||||
.left => if (this.hovered > 0) this.hovered - 1 else return null,
|
||||
else => return null,
|
||||
};
|
||||
|
||||
var filled_space: f32 = 0;
|
||||
for (this.children.items) |child| {
|
||||
const child_size = child.interface.minimum_size(child.pointer, render_resources);
|
||||
|
||||
filled_space += child_size[0];
|
||||
}
|
||||
|
||||
const empty_space = parent_size[0] - filled_space;
|
||||
|
||||
const space_around = empty_space / @as(f32, @floatFromInt((this.children.items.len + 1)));
|
||||
|
||||
var x: f32 = min[0] + space_around;
|
||||
for (this.children.items) |child| {
|
||||
const child_size = child.interface.minimum_size(child.pointer, render_resources);
|
||||
|
||||
const child_min = [2]f32{ x, min[1] };
|
||||
const child_max = [2]f32{ x + child_size[0], max[1] };
|
||||
|
||||
try child.interface.get_actions(child.pointer, actions, render_resources, child_min, child_max);
|
||||
|
||||
x += child_size[0] + space_around;
|
||||
}
|
||||
this.hovered = new_hovered;
|
||||
return Element.Command.none;
|
||||
}
|
||||
|
||||
const Element = @import("../Element.zig");
|
||||
|
|
|
@ -21,7 +21,7 @@ pub fn element(this: *@This()) Element {
|
|||
.destroy = &element_destroy,
|
||||
.minimum_size = &element_minimum_size,
|
||||
.render = &element_render,
|
||||
.get_actions = &element_get_actions,
|
||||
.event = &element_event,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resou
|
|||
this.child.interface.render(this.child.pointer, canvas, render_resources, min, max);
|
||||
|
||||
// render selection indicator on top if this link is selected
|
||||
if (render_resources.hovered != null and std.meta.eql(render_resources.hovered.?, Element.Command{ .goto_reference = this.reference })) {
|
||||
if (render_resources.hovered) {
|
||||
canvas.rect(
|
||||
.{ min[0], min[1] },
|
||||
.{ max[0] - min[0], max[1] - min[1] },
|
||||
|
@ -53,18 +53,13 @@ pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resou
|
|||
}
|
||||
}
|
||||
|
||||
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Element.Action), render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
|
||||
pub fn element_event(pointer: ?*anyopaque, event: Element.Event) ?Element.Command {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
_ = render_resources;
|
||||
|
||||
try actions.append(.{
|
||||
.center = [2]f32{
|
||||
(min[0] + max[0]) / 2,
|
||||
(min[1] + max[1]) / 2,
|
||||
},
|
||||
.command = .{ .goto_reference = this.reference },
|
||||
});
|
||||
switch (event) {
|
||||
.activate => return Element.Command{ .goto_reference = this.reference },
|
||||
else => return null,
|
||||
}
|
||||
}
|
||||
|
||||
const Element = @import("../Element.zig");
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
allocator: std.mem.Allocator,
|
||||
children: std.ArrayListUnmanaged(Child),
|
||||
hovered: usize,
|
||||
|
||||
pub const Child = struct {
|
||||
/// Where is the child attached on the parent? Imagine it as a pin going through
|
||||
|
@ -25,6 +26,7 @@ pub fn create(allocator: std.mem.Allocator) !*@This() {
|
|||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.children = .{},
|
||||
.hovered = 0,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
@ -44,7 +46,7 @@ pub fn element(this: *@This()) Element {
|
|||
.destroy = &element_destroy,
|
||||
.minimum_size = &element_minimum_size,
|
||||
.render = &element_render,
|
||||
.get_actions = &element_get_actions,
|
||||
.event = &element_event,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -82,7 +84,7 @@ pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resou
|
|||
max[1] - min[1],
|
||||
};
|
||||
|
||||
for (this.children.items) |child| {
|
||||
for (this.children.items, 0..) |child, child_index| {
|
||||
const pos_in_parent = [2]f32{
|
||||
child.anchor_in_parent[0] * parent_size[0],
|
||||
child.anchor_in_parent[1] * parent_size[1],
|
||||
|
@ -104,41 +106,69 @@ pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resou
|
|||
@min(max[1], pos_in_parent[1] + (child_size[1] - pos_in_child[1])),
|
||||
};
|
||||
|
||||
child.element.interface.render(child.element.pointer, canvas, render_resources, child_min, child_max);
|
||||
var child_render_resources = render_resources;
|
||||
child_render_resources.hovered = render_resources.hovered and child_index == this.hovered;
|
||||
|
||||
child.element.interface.render(child.element.pointer, canvas, child_render_resources, child_min, child_max);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Element.Action), render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
|
||||
pub fn element_event(pointer: ?*anyopaque, event: Element.Event) ?Element.Command {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
const parent_size = [2]f32{
|
||||
max[0] - min[0],
|
||||
max[1] - min[1],
|
||||
if (this.hovered < this.children.items.len) {
|
||||
const hovered = this.children.items[this.hovered].element;
|
||||
if (hovered.interface.event(hovered.pointer, event)) |cmd| {
|
||||
return cmd;
|
||||
}
|
||||
}
|
||||
|
||||
const direction: [2]f32 = switch (event) {
|
||||
.up => .{ 0, -1 },
|
||||
.right => .{ 1, 0 },
|
||||
.down => .{ 0, 1 },
|
||||
.left => .{ -1, 0 },
|
||||
else => return null,
|
||||
};
|
||||
|
||||
for (this.children.items) |child| {
|
||||
const pos_in_parent = [2]f32{
|
||||
child.anchor_in_parent[0] * parent_size[0],
|
||||
child.anchor_in_parent[1] * parent_size[1],
|
||||
};
|
||||
const current_center: [2]f32 = if (this.hovered < this.children.items.len) this.children.items[this.hovered].anchor_in_parent else .{ 0.5, 0.5 };
|
||||
|
||||
const child_size = child.element.interface.minimum_size(child.element.pointer, render_resources);
|
||||
var new_distance: ?f32 = null;
|
||||
var new_hovered: ?usize = null;
|
||||
for (this.children.items, 0..) |child, child_index| {
|
||||
if (child_index == this.hovered) {
|
||||
continue;
|
||||
}
|
||||
if (distanceToAction(current_center, direction, child.anchor_in_parent)) |distance| {
|
||||
if (new_distance == null or (new_distance != null and distance < new_distance.?)) {
|
||||
new_hovered = child_index;
|
||||
new_distance = distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const pos_in_child = [2]f32{
|
||||
child.anchor_in_child[0] * child_size[0],
|
||||
child.anchor_in_child[1] * child_size[1],
|
||||
};
|
||||
if (new_hovered) |h| {
|
||||
this.hovered = h;
|
||||
return Element.Command.none;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const child_min = [2]f32{
|
||||
pos_in_parent[0] - pos_in_child[0],
|
||||
pos_in_parent[1] - pos_in_child[1],
|
||||
};
|
||||
const child_max = [2]f32{
|
||||
pos_in_parent[0] + (child_size[0] - pos_in_child[0]),
|
||||
pos_in_parent[1] + (child_size[1] - pos_in_child[1]),
|
||||
};
|
||||
fn distanceToAction(origin: [2]f32, direction: [2]f32, other_pos: [2]f32) ?f32 {
|
||||
const off_axis_dir = [2]f32{ direction[1], -direction[0] };
|
||||
const offset = [2]f32{
|
||||
other_pos[0] - origin[0],
|
||||
other_pos[1] - origin[1],
|
||||
};
|
||||
|
||||
try child.element.interface.get_actions(child.element.pointer, actions, render_resources, child_min, child_max);
|
||||
// use dot product to check how in line with each direction the point of interest is
|
||||
const in_axis_distance = offset[0] * direction[0] + offset[1] * direction[1];
|
||||
const off_axis_distance = offset[0] * off_axis_dir[0] + offset[1] * off_axis_dir[1];
|
||||
|
||||
if (in_axis_distance > 0) {
|
||||
return in_axis_distance + off_axis_distance * off_axis_distance;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ pub fn element(this: *@This()) Element {
|
|||
.destroy = &element_destroy,
|
||||
.minimum_size = &element_minimum_size,
|
||||
.render = &element_render,
|
||||
.get_actions = &element_get_actions,
|
||||
.event = &element_event,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -46,12 +46,11 @@ pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resou
|
|||
_ = canvas.writeText(min, this.text, .{});
|
||||
}
|
||||
|
||||
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Element.Action), render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
|
||||
_ = pointer;
|
||||
_ = actions;
|
||||
_ = render_resources;
|
||||
_ = min;
|
||||
_ = max;
|
||||
pub fn element_event(pointer: ?*anyopaque, event: Element.Event) ?Element.Command {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
_ = this;
|
||||
_ = event;
|
||||
return null;
|
||||
}
|
||||
|
||||
const Element = @import("../Element.zig");
|
||||
|
|
|
@ -1,30 +1,27 @@
|
|||
allocator: std.mem.Allocator,
|
||||
reference: protocol.Reference,
|
||||
|
||||
handler_pointer: ?*anyopaque,
|
||||
response: ?protocol.Response,
|
||||
|
||||
link_actions_in_view: std.AutoHashMapUnmanaged(*LinkProxy, void) = .{},
|
||||
|
||||
const View = @This();
|
||||
|
||||
const LinkProxy = struct {
|
||||
view: *View,
|
||||
reference: protocol.Reference,
|
||||
|
||||
fn callback(pointer: ?*anyopaque) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
this.view.setReference(this.reference);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, reference: protocol.Reference) !*@This() {
|
||||
const this = try allocator.create(@This());
|
||||
errdefer allocator.destroy(this);
|
||||
|
||||
var handler_pointer: ?*anyopaque = null;
|
||||
switch (reference) {
|
||||
.handler => |interface| if (interface.create) |handler_create| {
|
||||
handler_pointer = try handler_create(allocator);
|
||||
},
|
||||
}
|
||||
|
||||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.reference = reference,
|
||||
.response = null,
|
||||
.handler_pointer = handler_pointer,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
@ -35,13 +32,18 @@ pub fn setReference(this: *@This(), reference: protocol.Reference) void {
|
|||
this.response = null;
|
||||
}
|
||||
|
||||
switch (this.reference) {
|
||||
.handler => |interface| if (interface.deinit) |deinit| deinit(this.handler_pointer),
|
||||
}
|
||||
this.handler_pointer = null;
|
||||
|
||||
this.reference = reference;
|
||||
|
||||
var action_proxies_iter = this.link_actions_in_view.keyIterator();
|
||||
while (action_proxies_iter.next()) |link_proxy| {
|
||||
this.allocator.destroy(link_proxy.*);
|
||||
switch (this.reference) {
|
||||
.handler => |interface| if (interface.create) |handler_create| {
|
||||
this.handler_pointer = handler_create(this.allocator) catch unreachable;
|
||||
},
|
||||
}
|
||||
this.link_actions_in_view.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
pub fn element(this: *@This()) Element {
|
||||
|
@ -51,7 +53,7 @@ pub fn element(this: *@This()) Element {
|
|||
.destroy = &element_destroy,
|
||||
.minimum_size = &element_minimum_size,
|
||||
.render = &element_render,
|
||||
.get_actions = &element_get_actions,
|
||||
.event = &element_event,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -67,14 +69,8 @@ pub fn element_destroy(pointer: ?*anyopaque) void {
|
|||
response.arena.deinit();
|
||||
}
|
||||
|
||||
var action_proxies_iter = this.link_actions_in_view.keyIterator();
|
||||
while (action_proxies_iter.next()) |link_proxy| {
|
||||
this.allocator.destroy(link_proxy.*);
|
||||
}
|
||||
this.link_actions_in_view.deinit(this.allocator);
|
||||
|
||||
switch (this.reference) {
|
||||
.handler => |handler| if (handler.interface.deinit) |deinit| deinit(handler.pointer),
|
||||
.handler => |interface| if (interface.deinit) |deinit| deinit(this.handler_pointer),
|
||||
}
|
||||
|
||||
this.allocator.destroy(this);
|
||||
|
@ -95,7 +91,7 @@ pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resou
|
|||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
if (this.response == null) {
|
||||
this.response = this.reference.handler.interface.handle(this.reference.handler.pointer, .{
|
||||
this.response = this.reference.handler.handle(this.handler_pointer, .{
|
||||
.allocator = this.allocator,
|
||||
}) catch response_error: {
|
||||
std.log.warn("Could not get response", .{});
|
||||
|
@ -104,70 +100,29 @@ pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resou
|
|||
}
|
||||
|
||||
if (this.response) |response| {
|
||||
var child_render_resources = render_resources;
|
||||
|
||||
if (render_resources.hovered) |hovered| {
|
||||
switch (hovered) {
|
||||
.callback => |cb| {
|
||||
if (this.link_actions_in_view.getKey(@ptrCast(@alignCast(cb.pointer)))) |link_proxy| {
|
||||
child_render_resources.hovered = .{ .goto_reference = link_proxy.reference };
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
switch (response.body) {
|
||||
.element => |root| root.interface.render(root.pointer, canvas, child_render_resources, min, max),
|
||||
.element => |root| root.interface.render(root.pointer, canvas, render_resources, min, max),
|
||||
}
|
||||
} else {
|
||||
_ = canvas.writeText(min, LOADING_TEXT, .{});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Element.Action), render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
|
||||
pub fn element_event(pointer: ?*anyopaque, event: Element.Event) ?Element.Command {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
// clean up previous link actions
|
||||
var action_proxies_iter = this.link_actions_in_view.keyIterator();
|
||||
while (action_proxies_iter.next()) |link_proxy| {
|
||||
this.allocator.destroy(link_proxy.*);
|
||||
}
|
||||
this.link_actions_in_view.clearRetainingCapacity();
|
||||
|
||||
var children_actions = std.ArrayList(Element.Action).init(this.allocator);
|
||||
defer children_actions.deinit();
|
||||
|
||||
if (this.response) |response| {
|
||||
switch (response.body) {
|
||||
.element => |root| {
|
||||
try root.interface.get_actions(root.pointer, &children_actions, render_resources, min, max);
|
||||
.element => |root| if (root.interface.event(root.pointer, event)) |cmd| switch (cmd) {
|
||||
.none => return Element.Command.none,
|
||||
.goto_reference => |ref| {
|
||||
this.setReference(ref);
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for (children_actions.items) |child_action| {
|
||||
switch (child_action.command) {
|
||||
.goto_reference => |ref| {
|
||||
const link_proxy_action = try this.allocator.create(LinkProxy);
|
||||
try this.link_actions_in_view.put(this.allocator, link_proxy_action, {});
|
||||
link_proxy_action.* = .{
|
||||
.view = this,
|
||||
.reference = ref,
|
||||
};
|
||||
try actions.append(.{
|
||||
.center = child_action.center,
|
||||
.command = .{
|
||||
.callback = .{
|
||||
.pointer = link_proxy_action,
|
||||
.function = &LinkProxy.callback,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
.callback => try actions.append(child_action),
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const Element = @import("../Element.zig");
|
||||
|
|
702
src/LocalUI.zig
702
src/LocalUI.zig
|
@ -1,9 +1,7 @@
|
|||
pub const main_menu = Handler{
|
||||
.pointer = null,
|
||||
.interface = &.{
|
||||
.handle = &main_menu_handler,
|
||||
.deinit = null,
|
||||
},
|
||||
pub const main_menu = &Handler.Interface{
|
||||
.create = null,
|
||||
.handle = &main_menu_handler,
|
||||
.deinit = null,
|
||||
};
|
||||
|
||||
fn main_menu_handler(_: ?*anyopaque, request: Request) Handler.Error!Response {
|
||||
|
@ -11,10 +9,10 @@ fn main_menu_handler(_: ?*anyopaque, request: Request) Handler.Error!Response {
|
|||
errdefer arena.deinit();
|
||||
|
||||
var join_multiplayer_text = try Element.Text.create(arena.allocator(), "Join Multiplayer Game");
|
||||
var join_multiplayer_link = try Element.Link.create(arena.allocator(), join_multiplayer_text.element(), .{ .handler = join_multiplayer_game });
|
||||
var join_multiplayer_link = try Element.Link.create(arena.allocator(), join_multiplayer_text.element(), .{ .handler = JoinMultiplayerGame.INTERFACE });
|
||||
|
||||
var host_multiplayer_text = try Element.Text.create(arena.allocator(), "Host Multiplayer Game");
|
||||
var host_multiplayer_link = try Element.Link.create(arena.allocator(), host_multiplayer_text.element(), .{ .handler = host_multiplayer_game });
|
||||
var host_multiplayer_link = try Element.Link.create(arena.allocator(), host_multiplayer_text.element(), .{ .handler = HostMultiplayerGame.INTERFACE });
|
||||
|
||||
var play_game_hbox = try Element.HBox.create(arena.allocator());
|
||||
try play_game_hbox.addElement(join_multiplayer_link.element());
|
||||
|
@ -26,50 +24,674 @@ fn main_menu_handler(_: ?*anyopaque, request: Request) Handler.Error!Response {
|
|||
return Response{ .arena = arena, .body = .{ .element = page.element() } };
|
||||
}
|
||||
|
||||
pub const join_multiplayer_game = Handler{
|
||||
.pointer = null,
|
||||
.interface = &.{
|
||||
.handle = &join_multiplayer_game_handler,
|
||||
.deinit = null,
|
||||
},
|
||||
const JoinMultiplayerGame = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
discovery_thread: std.Thread,
|
||||
|
||||
pub const INTERFACE = &Handler.Interface{
|
||||
.create = &create,
|
||||
.handle = &handler,
|
||||
.deinit = &deinit,
|
||||
};
|
||||
|
||||
fn create(allocator: std.mem.Allocator) Handler.Error!?*anyopaque {
|
||||
const this = try allocator.create(@This());
|
||||
errdefer allocator.destroy(this);
|
||||
|
||||
const discovery_thread = std.Thread.spawn(.{}, find_local_games_thread, .{allocator}) catch return error.OutOfMemory;
|
||||
|
||||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.discovery_thread = discovery_thread,
|
||||
};
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
fn handler(pointer: ?*anyopaque, request: Request) Handler.Error!Response {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
_ = this;
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(request.allocator);
|
||||
errdefer arena.deinit();
|
||||
|
||||
var text = try Element.Text.create(arena.allocator(), try std.fmt.allocPrint(arena.allocator(), "Looking for multiplayer games", .{}));
|
||||
|
||||
var page = try Element.Page.create(request.allocator);
|
||||
try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, text.element());
|
||||
|
||||
return Response{ .arena = arena, .body = .{ .element = page.element() } };
|
||||
}
|
||||
|
||||
fn deinit(pointer: ?*anyopaque) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
this.allocator.destroy(this);
|
||||
}
|
||||
|
||||
fn find_local_games_thread(allocator: std.mem.Allocator) !void {
|
||||
const buffer = try allocator.alloc(u8, 2048);
|
||||
defer allocator.free(buffer);
|
||||
|
||||
const log = std.log.scoped(.mdns);
|
||||
log.info("mdns discovery service started", .{});
|
||||
|
||||
// IPv4
|
||||
const mdns_ipv4_socket = c.mdns_socket_open_ipv4(null);
|
||||
defer c.mdns_socket_close(mdns_ipv4_socket);
|
||||
|
||||
// IPv6
|
||||
const mdns_ipv6_socket = c.mdns_socket_open_ipv6(null);
|
||||
defer c.mdns_socket_close(mdns_ipv6_socket);
|
||||
|
||||
const service_name = "rummy._udp.local.";
|
||||
for (&[_]c_int{ mdns_ipv4_socket, mdns_ipv6_socket }) |socket| {
|
||||
switch (c.mdns_query_send(socket, c.MDNS_RECORDTYPE_PTR, service_name.ptr, service_name.len, buffer.ptr, buffer.len, 0)) {
|
||||
0 => {},
|
||||
else => |err_code| log.warn("Failed to send question; error code = {}", .{err_code}),
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
var pollfds = [_]std.os.pollfd{
|
||||
.{ .fd = mdns_ipv4_socket, .events = std.os.POLL.IN, .revents = undefined },
|
||||
.{ .fd = mdns_ipv6_socket, .events = std.os.POLL.IN, .revents = undefined },
|
||||
};
|
||||
_ = std.os.poll(&pollfds, 1000) catch break;
|
||||
for (pollfds) |pollfd| {
|
||||
if (pollfd.revents & std.os.POLL.IN == 0) continue;
|
||||
_ = c.mdns_query_recv(pollfd.fd, buffer.ptr, buffer.len, &find_local_games_thread_query_callback, null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("mdns service stopping", .{});
|
||||
}
|
||||
|
||||
fn find_local_games_thread_query_callback(
|
||||
sock: c_int,
|
||||
from: ?*const c.sockaddr,
|
||||
addrlen: usize,
|
||||
entry: c.mdns_entry_type_t,
|
||||
query_id: u16,
|
||||
rtype: u16,
|
||||
rclass: u16,
|
||||
ttl: u32,
|
||||
data_ptr_opaque: ?*const anyopaque,
|
||||
data_len: usize,
|
||||
name_offset: usize,
|
||||
name_length: usize,
|
||||
record_offset: usize,
|
||||
record_length: usize,
|
||||
userdata: ?*anyopaque,
|
||||
) callconv(.C) c_int {
|
||||
const log = std.log.scoped(.mdns);
|
||||
|
||||
_ = rclass;
|
||||
_ = userdata;
|
||||
_ = ttl;
|
||||
_ = name_length;
|
||||
_ = sock;
|
||||
_ = addrlen;
|
||||
_ = query_id;
|
||||
|
||||
// const data_ptr: [*]const u8 = @ptrCast(data_ptr_opaque.?);
|
||||
// const data = data_ptr[0..data_len];
|
||||
|
||||
var name_buffer: [128]u8 = undefined;
|
||||
var offset = name_offset;
|
||||
const name_mdns_str = c.mdns_string_extract(data_ptr_opaque, data_len, &offset, &name_buffer, name_buffer.len);
|
||||
const name = name_mdns_str.str[0..name_mdns_str.length];
|
||||
|
||||
const std_from: ?std.net.Address = if (from) |f| std.net.Address{ .any = @bitCast(f.*) } else null;
|
||||
|
||||
const entrytype_str = switch (entry) {
|
||||
c.MDNS_ENTRYTYPE_QUESTION => "question",
|
||||
c.MDNS_ENTRYTYPE_ANSWER => "answer",
|
||||
c.MDNS_ENTRYTYPE_ADDITIONAL => "additional",
|
||||
c.MDNS_ENTRYTYPE_AUTHORITY => "authority",
|
||||
else => "???",
|
||||
};
|
||||
const recordtype_str = switch (rtype) {
|
||||
c.MDNS_RECORDTYPE_PTR => "PTR",
|
||||
c.MDNS_RECORDTYPE_SRV => "SRV",
|
||||
c.MDNS_RECORDTYPE_A => "A",
|
||||
c.MDNS_RECORDTYPE_AAAA => "AAAA",
|
||||
c.MDNS_RECORDTYPE_TXT => "TXT",
|
||||
else => "???",
|
||||
};
|
||||
if (rtype == c.MDNS_RECORDTYPE_PTR) {
|
||||
log.info("{?} : {s} {s} \"{}\"", .{ std_from, entrytype_str, recordtype_str, std.zig.fmtEscapes(name) });
|
||||
}
|
||||
|
||||
switch (rtype) {
|
||||
c.MDNS_RECORDTYPE_PTR => {
|
||||
var namebuffer: [128]u8 = undefined;
|
||||
const namestr = c.mdns_record_parse_ptr(data_ptr_opaque, data_len, record_offset, record_length, &namebuffer, namebuffer.len);
|
||||
log.info("{?} : {s} {s} \"{}\"", .{ std_from, entrytype_str, recordtype_str, std.zig.fmtEscapes(namestr.str[0..namestr.length]) });
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
fn join_multiplayer_game_handler(_: ?*anyopaque, request: Request) Handler.Error!Response {
|
||||
var arena = std.heap.ArenaAllocator.init(request.allocator);
|
||||
errdefer arena.deinit();
|
||||
const HostMultiplayerGame = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
mdns_addr4_string: []u8,
|
||||
mdns_addr6_string: []u8,
|
||||
|
||||
var text = try Element.Text.create(arena.allocator(), "Joining Multiplayer Game");
|
||||
mdns_socket4: std.os.socket_t,
|
||||
mdns_socket6: std.os.socket_t,
|
||||
|
||||
var page = try Element.Page.create(arena.allocator());
|
||||
try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, text.element());
|
||||
service_string: []const u8,
|
||||
service_instance_string: []const u8,
|
||||
qualified_hostname: []const u8,
|
||||
mdns_records: []c.mdns_record_t,
|
||||
mdns_thread: std.Thread,
|
||||
mdns_thread_should_stop: std.atomic.Value(bool),
|
||||
|
||||
return Response{ .arena = arena, .body = .{ .element = page.element() } };
|
||||
}
|
||||
address: c.ENetAddress,
|
||||
server: *c.ENetHost,
|
||||
|
||||
pub const host_multiplayer_game = Handler{
|
||||
.pointer = null,
|
||||
.interface = &.{
|
||||
.handle = &host_multiplayer_game_handler,
|
||||
.deinit = null,
|
||||
},
|
||||
pub const INTERFACE = &Handler.Interface{
|
||||
.create = &create,
|
||||
.handle = &handler,
|
||||
.deinit = &deinit,
|
||||
};
|
||||
|
||||
fn create(allocator: std.mem.Allocator) Handler.Error!?*anyopaque {
|
||||
const this = try allocator.create(@This());
|
||||
errdefer allocator.destroy(this);
|
||||
|
||||
// setup ENet game server
|
||||
const address = c.ENetAddress{
|
||||
.host = c.ENET_HOST_ANY,
|
||||
.port = 5711,
|
||||
};
|
||||
|
||||
const server = c.enet_host_create(&address, 4, 2, 0, 0);
|
||||
if (server == null) {
|
||||
return error.OutOfMemory; // TODO: add better error message
|
||||
}
|
||||
|
||||
// setup mDNS discovery service
|
||||
|
||||
const mdns_socket = std.os.socket(std.os.AF.INET, std.os.SOCK.DGRAM, std.os.IPPROTO.UDP) catch |e| {
|
||||
std.log.scoped(.mdns).err("failed to open mdns socket = {}", .{e});
|
||||
return error.OutOfMemory;
|
||||
};
|
||||
|
||||
std.os.setsockopt(
|
||||
mdns_socket,
|
||||
std.os.SOL.SOCKET,
|
||||
std.os.SO.REUSEADDR,
|
||||
&std.mem.toBytes(@as(c_int, 1)),
|
||||
) catch unreachable;
|
||||
std.os.setsockopt(
|
||||
mdns_socket,
|
||||
std.os.SOL.SOCKET,
|
||||
std.os.SO.REUSEPORT,
|
||||
&std.mem.toBytes(@as(c_int, 1)),
|
||||
) catch unreachable;
|
||||
std.os.setsockopt(
|
||||
mdns_socket,
|
||||
std.os.IPPROTO.IP,
|
||||
std.os.linux.IP.MULTICAST_TTL,
|
||||
&std.mem.toBytes(@as(c_int, 1)),
|
||||
) catch unreachable;
|
||||
std.os.setsockopt(
|
||||
mdns_socket,
|
||||
std.os.IPPROTO.IP,
|
||||
std.os.linux.IP.MULTICAST_LOOP,
|
||||
&std.mem.toBytes(@as(c_int, 1)),
|
||||
) catch unreachable;
|
||||
|
||||
const ip_mreq_t = extern struct {
|
||||
multicast_address: [4]u8,
|
||||
address: [4]u8,
|
||||
};
|
||||
std.os.setsockopt(
|
||||
mdns_socket,
|
||||
std.os.IPPROTO.IP,
|
||||
std.os.linux.IP.ADD_MEMBERSHIP,
|
||||
&std.mem.toBytes(ip_mreq_t{
|
||||
.multicast_address = .{ 224, 0, 0, 251 },
|
||||
.address = .{ 0, 0, 0, 0 },
|
||||
}),
|
||||
) catch unreachable;
|
||||
|
||||
const mdns_sock_addr = std.net.Address.parseIp4("0.0.0.0", c.MDNS_PORT) catch unreachable;
|
||||
std.os.bind(mdns_socket, @ptrCast(&mdns_sock_addr), mdns_sock_addr.getOsSockLen()) catch |e| {
|
||||
std.log.scoped(.mdns).err("bind failed = {}", .{e});
|
||||
return error.OutOfMemory;
|
||||
};
|
||||
|
||||
std.os.setsockopt(
|
||||
mdns_socket,
|
||||
std.os.IPPROTO.IP,
|
||||
std.os.linux.IP.MULTICAST_IF,
|
||||
&std.mem.toBytes(mdns_sock_addr.any),
|
||||
) catch unreachable;
|
||||
|
||||
// ipv6 socket
|
||||
const mdns_socket6 = std.os.socket(std.os.AF.INET6, std.os.SOCK.DGRAM, std.os.IPPROTO.UDP) catch |e| {
|
||||
std.log.scoped(.mdns).err("failed to open mdns socket = {}", .{e});
|
||||
return error.OutOfMemory;
|
||||
};
|
||||
|
||||
std.os.setsockopt(
|
||||
mdns_socket6,
|
||||
std.os.SOL.SOCKET,
|
||||
std.os.SO.REUSEADDR,
|
||||
&std.mem.toBytes(@as(c_int, 1)),
|
||||
) catch unreachable;
|
||||
std.os.setsockopt(
|
||||
mdns_socket6,
|
||||
std.os.SOL.SOCKET,
|
||||
std.os.SO.REUSEPORT,
|
||||
&std.mem.toBytes(@as(c_int, 1)),
|
||||
) catch unreachable;
|
||||
std.os.setsockopt(
|
||||
mdns_socket6,
|
||||
std.os.IPPROTO.IPV6,
|
||||
std.os.linux.IPV6.MULTICAST_HOPS,
|
||||
&std.mem.toBytes(@as(c_int, 1)),
|
||||
) catch unreachable;
|
||||
std.os.setsockopt(
|
||||
mdns_socket6,
|
||||
std.os.IPPROTO.IPV6,
|
||||
std.os.linux.IPV6.MULTICAST_LOOP,
|
||||
&std.mem.toBytes(@as(c_int, 1)),
|
||||
) catch unreachable;
|
||||
std.os.setsockopt(
|
||||
mdns_socket6,
|
||||
std.os.IPPROTO.IPV6,
|
||||
std.os.linux.IPV6.MULTICAST_IF,
|
||||
&std.mem.toBytes(@as(c_int, 0)),
|
||||
) catch unreachable;
|
||||
|
||||
const ipv6_mreq_t = extern struct {
|
||||
addr: [16]u8,
|
||||
interface: c_int,
|
||||
};
|
||||
|
||||
// TODO: get ipv4 and ipv6 adress of server to send out over mDNS
|
||||
// const SIOCGIFADDR = 0x8915;
|
||||
// var ifr: std.os.ifreq = undefined;
|
||||
// ifr.ifru.addr.family = std.os.AF.INET;
|
||||
// const interface_name = "wlp170s0";
|
||||
// @memset(&ifr.ifrn.name, 0);
|
||||
// @memcpy(ifr.ifrn.name[0..interface_name.len], interface_name);
|
||||
|
||||
// const ifr_ret_value = std.os.linux.getErrno(std.os.linux.ioctl(mdns_socket6, SIOCGIFADDR, @intFromPtr(&ifr)));
|
||||
// std.log.scoped(.mdns).debug("ioctl({}, SIOCGIFADDR, {*}) = {}", .{ mdns_socket6, &ifr, ifr_ret_value });
|
||||
|
||||
// std.os.ioctl_SIOCGIFINDEX(mdns_socket6, &ifr) catch unreachable;
|
||||
// const interface = ifr.ifru.ivalue;
|
||||
|
||||
// const mdns_ipv6_multicast_group = std.net.Address.parseIp6("ff02::fb", c.MDNS_PORT) catch unreachable;
|
||||
// var socklen = mdns_sock_addr6.getOsSockLen();
|
||||
std.os.setsockopt(
|
||||
mdns_socket6,
|
||||
std.os.IPPROTO.IPV6,
|
||||
std.os.linux.IPV6.ADD_MEMBERSHIP,
|
||||
&std.mem.toBytes(ipv6_mreq_t{
|
||||
.addr = .{ 0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFB },
|
||||
.interface = 0,
|
||||
}),
|
||||
) catch unreachable;
|
||||
|
||||
const mdns_sock_addr6 = std.net.Address.parseIp6("::", c.MDNS_PORT) catch unreachable;
|
||||
var socklen = mdns_sock_addr6.getOsSockLen();
|
||||
std.os.bind(mdns_socket6, &mdns_sock_addr6.any, socklen) catch unreachable;
|
||||
// std.os.listen(mdns_socket6, self.kernel_backlog) catch unreachable;
|
||||
|
||||
var mdns_listen_addr: std.net.Address = undefined;
|
||||
std.os.getsockname(mdns_socket, &mdns_listen_addr.any, &socklen) catch unreachable;
|
||||
const ipv4_sockaddr_string = try std.fmt.allocPrint(allocator, "{}", .{mdns_listen_addr});
|
||||
|
||||
socklen = mdns_sock_addr6.getOsSockLen();
|
||||
var mdns_listen_addr6: std.net.Address = undefined;
|
||||
std.os.getsockname(mdns_socket6, &mdns_listen_addr6.any, &socklen) catch unreachable;
|
||||
const ipv6_sockaddr_string = try std.fmt.allocPrint(allocator, "{}", .{mdns_listen_addr6});
|
||||
|
||||
// // const SIOCGIFNAME = 0x8910;
|
||||
|
||||
// const ipv4_sockaddr: std.os.sockaddr.in = @bitCast(ifr.ifru.addr);
|
||||
// // const ipv4_bytes: [4]u8 = @bitCast(ipv4_sockaddr.addr);
|
||||
|
||||
const service_string = try allocator.dupe(u8, "rummy._udp.local.");
|
||||
|
||||
var hostname_buffer: [std.os.HOST_NAME_MAX]u8 = undefined;
|
||||
const hostname = std.os.gethostname(&hostname_buffer) catch unreachable;
|
||||
|
||||
// "<hostname>.<_service-name>._udp.local." string
|
||||
const service_instance_string = try std.fmt.allocPrint(allocator, "{s}.{s}", .{
|
||||
hostname,
|
||||
service_string,
|
||||
});
|
||||
errdefer allocator.free(service_string);
|
||||
|
||||
const qualified_hostname = try std.fmt.allocPrint(allocator, "{s}.local.", .{hostname});
|
||||
errdefer allocator.free(qualified_hostname);
|
||||
std.log.scoped(.mdns).info("qualified hostname = \"{}\"", .{std.zig.fmtEscapes(qualified_hostname)});
|
||||
|
||||
// Create DNS records
|
||||
|
||||
var mdns_records = std.ArrayList(c.mdns_record_t).init(allocator);
|
||||
defer mdns_records.deinit();
|
||||
|
||||
// DNS PTR that maps "<service_string>._udp.local." to "<hostname>.<service_string>._udp.local."
|
||||
try mdns_records.append(c.mdns_record_t{
|
||||
.name = .{ .str = service_string.ptr, .length = service_string.len },
|
||||
.type = c.MDNS_RECORDTYPE_PTR,
|
||||
.data = .{ .ptr = .{ .name = .{ .str = service_instance_string.ptr, .length = service_instance_string.len } } },
|
||||
.rclass = 0,
|
||||
.ttl = 0,
|
||||
});
|
||||
|
||||
// DNS SRV that maps "<hostname>.<service_string>._udp.local." to "<hostname>.local.:<PORT>"
|
||||
try mdns_records.append(c.mdns_record_t{
|
||||
.name = .{ .str = service_instance_string.ptr, .length = service_instance_string.len },
|
||||
.type = c.MDNS_RECORDTYPE_SRV,
|
||||
.data = .{ .srv = .{
|
||||
.name = .{ .str = qualified_hostname.ptr, .length = qualified_hostname.len },
|
||||
.port = address.port,
|
||||
.priority = 0,
|
||||
.weight = 0,
|
||||
} },
|
||||
.rclass = 0,
|
||||
.ttl = 0,
|
||||
});
|
||||
|
||||
// TODO: Add AAAA and A records to mdns service
|
||||
// // DNS A that maps "<hostname>.local." to the an ip address
|
||||
// const mdns_a_record = c.mdns_record_t{
|
||||
// .name = .{ .str = qualified_hostname.ptr, .length = qualified_hostname.len },
|
||||
// .type = c.MDNS_RECORDTYPE_A,
|
||||
// .data = .{ .a = .{
|
||||
// .addr = @bitCast(ipv4_sockaddr),
|
||||
// } },
|
||||
// .rclass = 0,
|
||||
// .ttl = 0,
|
||||
// };
|
||||
// try mdns_records.append(mdns_a_record);
|
||||
|
||||
const mdns_record_slice = try mdns_records.toOwnedSlice();
|
||||
errdefer allocator.free(mdns_record_slice);
|
||||
|
||||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.mdns_addr4_string = ipv4_sockaddr_string,
|
||||
.mdns_addr6_string = ipv6_sockaddr_string,
|
||||
.mdns_socket4 = mdns_socket,
|
||||
.mdns_socket6 = mdns_socket6,
|
||||
.service_string = service_string,
|
||||
.service_instance_string = service_instance_string,
|
||||
.qualified_hostname = qualified_hostname,
|
||||
.mdns_records = mdns_record_slice,
|
||||
.mdns_thread = undefined,
|
||||
.mdns_thread_should_stop = std.atomic.Value(bool).init(false),
|
||||
|
||||
.address = address,
|
||||
.server = server,
|
||||
};
|
||||
|
||||
this.mdns_thread = std.Thread.spawn(.{}, mdns_thread_main, .{this}) catch return error.OutOfMemory;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
fn handler(pointer: ?*anyopaque, request: Request) Handler.Error!Response {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(request.allocator);
|
||||
errdefer arena.deinit();
|
||||
|
||||
// var text = try Element.Text.create(arena.allocator(), try std.fmt.allocPrint(arena.allocator(), "Hosting Multiplayer Game at {}:{}", .{ this.address.host, this.address.port }));
|
||||
var mdns_addr4_string_text = try Element.Text.create(request.allocator, this.mdns_addr4_string);
|
||||
var mdns_addr6_string_text = try Element.Text.create(request.allocator, this.mdns_addr6_string);
|
||||
var service_instance_string_text = try Element.Text.create(request.allocator, this.service_instance_string);
|
||||
var qualified_hostname_text = try Element.Text.create(request.allocator, this.qualified_hostname);
|
||||
|
||||
var page = try Element.Page.create(request.allocator);
|
||||
// try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, text.element());
|
||||
try page.addElement(.{ 0.3, 0.6 }, .{ 0.5, 0.5 }, mdns_addr4_string_text.element());
|
||||
try page.addElement(.{ 0.7, 0.6 }, .{ 0.5, 0.5 }, mdns_addr6_string_text.element());
|
||||
try page.addElement(.{ 0.5, 0.7 }, .{ 0.5, 0.5 }, service_instance_string_text.element());
|
||||
try page.addElement(.{ 0.5, 0.9 }, .{ 0.5, 0.5 }, qualified_hostname_text.element());
|
||||
|
||||
return Response{ .arena = arena, .body = .{ .element = page.element() } };
|
||||
}
|
||||
|
||||
fn deinit(pointer: ?*anyopaque) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
c.enet_host_destroy(this.server);
|
||||
|
||||
this.mdns_thread_should_stop.store(true, .Monotonic);
|
||||
this.mdns_thread.join();
|
||||
|
||||
this.allocator.free(this.mdns_records);
|
||||
this.allocator.free(this.mdns_addr4_string);
|
||||
this.allocator.free(this.mdns_addr6_string);
|
||||
this.allocator.free(this.service_string);
|
||||
this.allocator.free(this.service_instance_string);
|
||||
this.allocator.free(this.qualified_hostname);
|
||||
|
||||
this.allocator.destroy(this);
|
||||
}
|
||||
|
||||
fn mdns_thread_main(this: *@This()) !void {
|
||||
const buffer = try this.allocator.alloc(u8, 2048);
|
||||
defer this.allocator.free(buffer);
|
||||
|
||||
const log = std.log.scoped(.mdns);
|
||||
log.info("mdns service started", .{});
|
||||
|
||||
_ = c.mdns_announce_multicast(
|
||||
this.mdns_socket4,
|
||||
buffer.ptr,
|
||||
buffer.len,
|
||||
this.mdns_records[0],
|
||||
null,
|
||||
0,
|
||||
this.mdns_records[1..].ptr,
|
||||
this.mdns_records[1..].len,
|
||||
);
|
||||
_ = c.mdns_announce_multicast(
|
||||
this.mdns_socket6,
|
||||
buffer.ptr,
|
||||
buffer.len,
|
||||
this.mdns_records[0],
|
||||
null,
|
||||
0,
|
||||
this.mdns_records[1..].ptr,
|
||||
this.mdns_records[1..].len,
|
||||
);
|
||||
|
||||
while (!this.mdns_thread_should_stop.load(.Monotonic)) {
|
||||
var pollfds = [_]std.os.pollfd{
|
||||
.{ .fd = this.mdns_socket4, .events = std.os.POLL.IN, .revents = undefined },
|
||||
.{ .fd = this.mdns_socket6, .events = std.os.POLL.IN, .revents = undefined },
|
||||
};
|
||||
_ = std.os.poll(&pollfds, 1000) catch break;
|
||||
for (pollfds) |pollfd| {
|
||||
if (pollfd.revents & std.os.POLL.IN == 0) continue;
|
||||
_ = c.mdns_socket_listen(pollfd.fd, buffer.ptr, buffer.len, &mdns_thread_service_callback, this);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("mdns service stopping", .{});
|
||||
}
|
||||
|
||||
fn mdns_thread_service_callback(
|
||||
sock: c_int,
|
||||
from: ?*const c.sockaddr,
|
||||
addrlen: usize,
|
||||
entry: c.mdns_entry_type_t,
|
||||
query_id: u16,
|
||||
rtype: u16,
|
||||
rclass: u16,
|
||||
ttl: u32,
|
||||
data_ptr_opaque: ?*const anyopaque,
|
||||
data_len: usize,
|
||||
name_offset: usize,
|
||||
name_length: usize,
|
||||
record_offset: usize,
|
||||
record_length: usize,
|
||||
userdata: ?*anyopaque,
|
||||
) callconv(.C) c_int {
|
||||
const log = std.log.scoped(.mdns);
|
||||
if (entry != c.MDNS_ENTRYTYPE_QUESTION) return 0;
|
||||
|
||||
const DNS_SD = "_services._dns-sd._udp.local.";
|
||||
const this: *@This() = @ptrCast(@alignCast(userdata));
|
||||
_ = ttl;
|
||||
_ = record_offset;
|
||||
_ = record_length;
|
||||
_ = name_length;
|
||||
|
||||
// const data_ptr: [*]const u8 = @ptrCast(data_ptr_opaque.?);
|
||||
// const data = data_ptr[0..data_len];
|
||||
|
||||
var name_buffer: [128]u8 = undefined;
|
||||
var offset = name_offset;
|
||||
const name_mdns_str = c.mdns_string_extract(data_ptr_opaque, data_len, &offset, &name_buffer, name_buffer.len);
|
||||
const name = name_mdns_str.str[0..name_mdns_str.length];
|
||||
|
||||
const std_from: ?std.net.Address = if (from) |f| std.net.Address{ .any = @bitCast(f.*) } else null;
|
||||
|
||||
if (std.mem.eql(u8, name, DNS_SD)) {
|
||||
const answer = c.mdns_record_t{
|
||||
.name = .{ .str = name.ptr, .length = name.len },
|
||||
.type = c.MDNS_RECORDTYPE_PTR,
|
||||
.data = .{ .ptr = .{ .name = .{ .str = DNS_SD, .length = DNS_SD.len } } },
|
||||
};
|
||||
|
||||
var sendbuffer: [1024]u8 = undefined;
|
||||
if (rclass & c.MDNS_UNICAST_RESPONSE != 0) {
|
||||
const ret_val = c.mdns_query_answer_unicast(
|
||||
sock,
|
||||
from,
|
||||
addrlen,
|
||||
&sendbuffer,
|
||||
sendbuffer.len,
|
||||
query_id,
|
||||
rtype,
|
||||
name.ptr,
|
||||
name.len,
|
||||
answer,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
if (ret_val < 0) {
|
||||
log.warn("{s}:{} failed to answer query, error code = {}", .{ @src().file, @src().line, ret_val });
|
||||
}
|
||||
} else {
|
||||
const ret_val = c.mdns_query_answer_multicast(
|
||||
sock,
|
||||
&sendbuffer,
|
||||
sendbuffer.len,
|
||||
answer,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
if (ret_val < 0) {
|
||||
log.warn("{s}:{} failed to answer query, error code = {}", .{ @src().file, @src().line, ret_val });
|
||||
}
|
||||
}
|
||||
} else if (std.mem.eql(u8, name, this.service_string)) {
|
||||
const answer = this.mdns_records[0];
|
||||
const additional = this.mdns_records[1..];
|
||||
|
||||
log.debug("heard service name \"{}\", sending answer {} and {} additional records", .{ std.zig.fmtEscapes(name), answer, additional.len });
|
||||
|
||||
var sendbuffer: [1024]u8 = undefined;
|
||||
if (rclass & c.MDNS_UNICAST_RESPONSE != 0) {
|
||||
log.debug("{s}:{} unicast response to {?}", .{ @src().file, @src().line, std_from });
|
||||
const ret_val = c.mdns_query_answer_unicast(
|
||||
sock,
|
||||
from,
|
||||
addrlen,
|
||||
&sendbuffer,
|
||||
sendbuffer.len,
|
||||
query_id,
|
||||
rtype,
|
||||
name.ptr,
|
||||
name.len,
|
||||
answer,
|
||||
0,
|
||||
0,
|
||||
additional.ptr,
|
||||
additional.len,
|
||||
);
|
||||
if (ret_val < 0) {
|
||||
log.warn("{s}:{} failed to answer query with unicast, error code = {}", .{ @src().file, @src().line, ret_val });
|
||||
}
|
||||
} else {
|
||||
log.debug("{s}:{} multicast response", .{ @src().file, @src().line });
|
||||
const ret_val = c.mdns_query_answer_multicast(
|
||||
sock,
|
||||
&sendbuffer,
|
||||
sendbuffer.len,
|
||||
answer,
|
||||
0,
|
||||
0,
|
||||
additional.ptr,
|
||||
additional.len,
|
||||
);
|
||||
if (ret_val < 0) {
|
||||
log.warn("{s}:{} failed to answer query with multicast, error code = {}", .{ @src().file, @src().line, ret_val });
|
||||
}
|
||||
}
|
||||
} else if (std.mem.eql(u8, name, this.service_instance_string)) {
|
||||
const answer = this.mdns_records[1];
|
||||
|
||||
var sendbuffer: [1024]u8 = undefined;
|
||||
if (rclass & c.MDNS_UNICAST_RESPONSE != 0) {
|
||||
_ = c.mdns_query_answer_unicast(
|
||||
sock,
|
||||
from,
|
||||
addrlen,
|
||||
&sendbuffer,
|
||||
sendbuffer.len,
|
||||
query_id,
|
||||
rtype,
|
||||
name.ptr,
|
||||
name.len,
|
||||
answer,
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
0,
|
||||
);
|
||||
} else {
|
||||
_ = c.mdns_query_answer_multicast(
|
||||
sock,
|
||||
&sendbuffer,
|
||||
sendbuffer.len,
|
||||
answer,
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
fn host_multiplayer_game_handler(_: ?*anyopaque, request: Request) Handler.Error!Response {
|
||||
var arena = std.heap.ArenaAllocator.init(request.allocator);
|
||||
errdefer arena.deinit();
|
||||
|
||||
var text = try Element.Text.create(arena.allocator(), "Hosting Multiplayer Game");
|
||||
|
||||
var page = try Element.Page.create(arena.allocator());
|
||||
try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, text.element());
|
||||
|
||||
return Response{ .arena = arena, .body = .{ .element = page.element() } };
|
||||
}
|
||||
|
||||
const Handler = protocol.Handler;
|
||||
const Request = protocol.Request;
|
||||
const Response = protocol.Response;
|
||||
|
||||
const c = @import("./c.zig");
|
||||
const protocol = @import("./protocol.zig");
|
||||
const Element = @import("./Element.zig");
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
pub usingnamespace @cImport({
|
||||
@cInclude("enet/enet.h");
|
||||
@cInclude("mdns.h");
|
||||
});
|
115
src/main.zig
115
src/main.zig
|
@ -187,12 +187,6 @@ pub fn main() !void {
|
|||
const main_view = try Element.View.create(gpa.allocator(), .{ .handler = LocalUI.main_menu });
|
||||
defer main_view.element().interface.destroy(main_view.element().pointer);
|
||||
|
||||
var actions = std.ArrayList(Element.Action).init(gpa.allocator());
|
||||
defer actions.deinit();
|
||||
|
||||
// TODO: Restore hovered_action when undoing
|
||||
var hovered_action: ?Element.Action = null;
|
||||
|
||||
if (seizer.backend.glfw.c.glfwJoystickIsGamepad(seizer.backend.glfw.c.GLFW_JOYSTICK_1) != 0) {
|
||||
std.log.info("detected gamepad = \"{?s}\" {?s}", .{ seizer.backend.glfw.c.glfwGetGamepadName(seizer.backend.glfw.c.GLFW_JOYSTICK_1), seizer.backend.glfw.c.glfwGetJoystickGUID(seizer.backend.glfw.c.GLFW_JOYSTICK_1) });
|
||||
} else if (seizer.backend.glfw.c.glfwJoystickPresent(seizer.backend.glfw.c.GLFW_JOYSTICK_1) != 0) {
|
||||
|
@ -276,58 +270,49 @@ pub fn main() !void {
|
|||
prev_controller_input_state = controller_input_state;
|
||||
}
|
||||
|
||||
if (hovered_action) |hovered| {
|
||||
if (input_state.action) {
|
||||
switch (hovered.command) {
|
||||
.goto_reference => {
|
||||
std.debug.panic("Got `.goto_reference` action at top-level; expected `main_view` to proxy them all into LinkProxy callbacks.", .{});
|
||||
},
|
||||
.callback => |cb| {
|
||||
cb.function(cb.pointer);
|
||||
},
|
||||
}
|
||||
}
|
||||
} else if (actions.items.len > 0) {
|
||||
hovered_action = actions.items[0];
|
||||
}
|
||||
if (input_state.up) _ = main_view.element().interface.event(main_view.element().pointer, .up);
|
||||
if (input_state.right) _ = main_view.element().interface.event(main_view.element().pointer, .right);
|
||||
if (input_state.down) _ = main_view.element().interface.event(main_view.element().pointer, .down);
|
||||
if (input_state.left) _ = main_view.element().interface.event(main_view.element().pointer, .left);
|
||||
if (input_state.action) _ = main_view.element().interface.event(main_view.element().pointer, .activate);
|
||||
|
||||
if (hovered_action) |hovered| {
|
||||
var direction = [2]f32{ 0, 0 };
|
||||
if (input_state.left) {
|
||||
direction[0] -= 1;
|
||||
}
|
||||
if (input_state.right) {
|
||||
direction[0] += 1;
|
||||
}
|
||||
if (input_state.up) {
|
||||
direction[1] -= 1;
|
||||
}
|
||||
if (input_state.down) {
|
||||
direction[1] += 1;
|
||||
}
|
||||
// if (hovered_action) |hovered| {
|
||||
// var direction = [2]f32{ 0, 0 };
|
||||
// if (input_state.left) {
|
||||
// direction[0] -= 1;
|
||||
// }
|
||||
// if (input_state.right) {
|
||||
// direction[0] += 1;
|
||||
// }
|
||||
// if (input_state.up) {
|
||||
// direction[1] -= 1;
|
||||
// }
|
||||
// if (input_state.down) {
|
||||
// direction[1] += 1;
|
||||
// }
|
||||
|
||||
var new_distance: ?f32 = null;
|
||||
var new_action = hovered;
|
||||
for (actions.items) |action| {
|
||||
if (std.meta.eql(action.command, hovered.command)) {
|
||||
continue;
|
||||
}
|
||||
if (distanceToAction(hovered.center, direction, action.center)) |distance| {
|
||||
if (new_distance == null or (new_distance != null and distance < new_distance.?)) {
|
||||
new_action = Element.Action{
|
||||
.center = .{
|
||||
if (input_state.left or input_state.right) action.center[0] else hovered.center[0],
|
||||
if (input_state.up or input_state.down) action.center[1] else hovered.center[1],
|
||||
},
|
||||
.command = action.command,
|
||||
};
|
||||
new_distance = distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
// var new_distance: ?f32 = null;
|
||||
// var new_action = hovered;
|
||||
// for (actions.items) |action| {
|
||||
// if (std.meta.eql(action.command, hovered.command)) {
|
||||
// continue;
|
||||
// }
|
||||
// if (distanceToAction(hovered.center, direction, action.center)) |distance| {
|
||||
// if (new_distance == null or (new_distance != null and distance < new_distance.?)) {
|
||||
// new_action = Element.Action{
|
||||
// .center = .{
|
||||
// if (input_state.left or input_state.right) action.center[0] else hovered.center[0],
|
||||
// if (input_state.up or input_state.down) action.center[1] else hovered.center[1],
|
||||
// },
|
||||
// .command = action.command,
|
||||
// };
|
||||
// new_distance = distance;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
hovered_action = new_action;
|
||||
}
|
||||
// hovered_action = new_action;
|
||||
// }
|
||||
|
||||
gl.clearColor(0.7, 0.5, 0.5, 1.0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
@ -373,7 +358,7 @@ pub fn main() !void {
|
|||
main_view.element().pointer,
|
||||
&canvas,
|
||||
.{
|
||||
.hovered = if (hovered_action) |ha| ha.command else null,
|
||||
.hovered = true,
|
||||
.deck = deck_sprites,
|
||||
.font = canvas.font,
|
||||
},
|
||||
|
@ -384,27 +369,9 @@ pub fn main() !void {
|
|||
},
|
||||
);
|
||||
|
||||
_ = canvas.printText(.{ 0, 0 }, "#actions = {}", .{actions.items.len}, .{});
|
||||
|
||||
canvas.end();
|
||||
|
||||
seizer.backend.glfw.c.glfwSwapBuffers(window);
|
||||
|
||||
actions.clearRetainingCapacity();
|
||||
try main_view.element().interface.get_actions(
|
||||
main_view.element().pointer,
|
||||
&actions,
|
||||
.{
|
||||
.hovered = if (hovered_action) |ha| ha.command else null,
|
||||
.deck = deck_sprites,
|
||||
.font = canvas.font,
|
||||
},
|
||||
.{ 0, 0 },
|
||||
.{
|
||||
@floatFromInt(window_size[0]),
|
||||
@floatFromInt(window_size[1]),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,13 +5,14 @@ pub const Handler = struct {
|
|||
pub const Error = error{OutOfMemory};
|
||||
pub const Fn = *const fn (Request) Error!Response;
|
||||
pub const Interface = struct {
|
||||
create: ?*const fn (std.mem.Allocator) Error!?*anyopaque,
|
||||
handle: *const fn (?*anyopaque, Request) Error!Response,
|
||||
deinit: ?*const fn (?*anyopaque) void,
|
||||
};
|
||||
};
|
||||
|
||||
pub const Reference = union(enum) {
|
||||
handler: Handler,
|
||||
handler: *const Handler.Interface,
|
||||
};
|
||||
|
||||
pub const Request = struct {
|
||||
|
|
Loading…
Reference in New Issue