plac-for-apple-platform

Unofficial Roon client for Apple devices

core

Clients use this module and renders state struct managed by this. Actions are modeled as function returning void and clients listen to changes to the state via event callbacks.

// Sample definition:

// This dispatches an action without waiting for a result.
void plac_play_song(plac_app*, plac_song*);

// First argument is event payload and second argument is context/user data.
typedef void (*plac_cb_playback_change)(plac_song*, void*);

// The last argument is context/user data, which will be passed to the callback.
void plac_on_playback_change(plac_app*, plac_cb_playback_change, void*);

// Events without payload have the following signature.
typedef void (*plac_cb_foo)(void*);

Code Styles

This section describes module internal rules for writing code.

Struct creation function

A function that creates struct Foo,

This is what many Zig projects, including std, uses. Let the caller decide where to store.

An exported function (C API) should wrap init function and assign the result to an allocated space.

const std = @import("std");

extern struct Foo {
	bar: i32,

	pub fn init() Foo {
		return .{ bar = 0 };
	}

	pub fn make() callconv(.C) ?*Foo {
		const dst = std.heap.c_allocator.create(Foo) catch return null;
		dst.* = init();
		return dst;
	}
}

comptime {
	@export(&Foo.make, .{ .name = "plac_foo_make" });
}

Struct destroy function

A function that releases resources owned by struct Foo,

An exported function (C API) should wrap deinit function and free the pointer to itself.

const std = @import("std");

extern struct Foo {
	pub fn deinit(self: *const Foo) void {
		// ...
	}

	pub fn free(self_ptr: ?*const Foo) callconv(.C) void {
		const self = self_ptr orelse return;
		self.deinit();
		std.heap.c_allocator.destroy(self);
	}
}

comptime {
	@export(&Foo.free, .{ .name = "plac_foo_free" });
}

Function parameters

Zig team has a plan to drop automatic pass-by-reference conversion for function parameters. To minimize the cost of future migration, do not annotate a parameter with value type if pass-by-value is not desirable. For this reason, struct methods should use pointer type for self parameter.

struct Foo {
	// OK
	pub fn bar(self: *Foo): void {}

	// OK
	pub fn bar(self: *const Foo): void {}

	// NG
	pub fn bar(self: Foo): void {}
}

Exported functions

When an exported function takes a pointer, it should be an optional pointer rather than regular pointer, to guard against accidentally passing NULL pointer.

extern struct Foo {
	pub fn bar(self: *const Foo) void {
		// ...
	}
}

export fn do_something(foo_ptr: ?*const Foo) void {
	const foo = foo_ptr orelse return;
	foo.bar();
}

If the function returns result code, define a code for receiving a NULL pointer. If the function returns a pointer, return a NULL pointer and document about it in header file.