#"null function or function signature mismatch" in Emscripten builds
1 messages ยท Page 1 of 1 (latest)
Worth mentioning this does not happen on the PC builds
Could it be the dangers of @ptrCast at play?
shouldnt renderUpdate take an *anyopaque as its first arg and use ptrcast to get a *Behavior?
like this:
pub fn renderUpdate(ptr: *anyopaque, object: *GameObject) void {
const self: *Behavior = @ptrCast(ptr);
}
and yes ptrCasting function pointers is generally a bad idea
*anyopaque is a single pointer with an erased type, and you're casting the function into taking a Behavior (which is afaict an interface object) which is pretty far off from being a pointer
its not ptrcasting to get a *Behaviour
i have a bit of an interface approach
yeah, like writeAll shouldnt you be taking *anyopaque in your interface implementation and casting it to its actual type
yeah this came up when we were talking with mike and some folks and showing you this interface thing
I was suggested to move the logic to save an extra line of boilerplate at the first line of each method
it's not technically correct to do it the way you have, but it will probably work for native builds
it probably won't work here
I dont understand what the difference is though
Whys it save to do it on the first line of the method but not when setting the fn ptr
the simple thing to follow is, don't ever @ptrCast a function
you can @ptrCast a pointer to a struct
not to a function
(there are nuances to both, but this is a simplified rule)
so i should keep that boilerplate at the front of each method is what ur saying, and ditch the ptrcasts
even for more verbosity it would ensure working on web platform too
you really shouldnt be doing it on any platform
boooo wasm
;-;
so wait, even this would break on wasm?
youre giving it a function pointer that says it takes two pointers, but the function pointer actually takes a struct and a pointer
wait nvm, there it's ptrCasting the behaviour struct
not the function
I understand the issue and how to fix it but I was hoping there would be a way to do it that didnt involve me having to do that boilerplate line at the front of each fn, I guess not
to restablish the type
theres like, a way to do it without the boilerplate but it involves a bit of work in Behavior
eh:/ forget it
i can write an example
its not that bad
const Interface = struct {
ptr: *anyopaque,
vtable: *const Vtable,
const Vtable = struct {
func: *const fn (*anyopaque) void,
};
fn generate(comptime T: type) Vtable {
return .{
.func = struct {
fn f(ptr: *anyopaque) void {
T.func(@ptrCast(ptr));
}
}.f,
};
}
};
const Implementation = struct {
const myVtable = Interface.generate(Implementation);
pub fn func(self: *Implementation) void {
_ = self;
std.debug.print("hello\n", .{});
}
pub fn asInterface(self: *Implementation) Interface {
return .{ .ptr = self, .vtable = &myVtable };
}
};
pub fn main() !void {
var i: Implementation = .{};
const interface = i.asInterface();
interface.vtable.func(interface.ptr);
}
generate would be the function that wraps all the functions you need to do the ptrCast for you
so you trade having to @ptrCast at the start of every implenetation to having to write a wrapper for each vtable function
but all that can be hidden away
sounds like a good tradeoff
the idea is to have as least duplicated code as possible in behaviour files
yeah this would get rid of having to define a Behavior.VTable in each of your implementations too
oh and another tradeoff would be being "forced" to name your implementation functions a specific thing, but you could just pass each one in to generate to avoid that
i prefer the former since it would enforce consistency
@here I'm so dumb
It was because one of my functions signatures was incorrect
HA
I forgot a parameter and because of the ptrCast of the function
It screwed me up...
...
๐ญ
i feel so justified rn
Still, maybe a sign I should move away from this design...
I mean, to be fair
it was still ultimately because of the fn ptrcast
๐
i mean, sortaaaa
but that means instead of generating a wrapper, you could write a comptime signature checker
a userland fnPtrCast
true
(or cast the struct pointer in the generated code because it's easier to write it so it's always correct by construction)
but then you get goofy ass callstacks
like the frame with just the cast gets left in?
my bigger question would be whether ptrcasting the function defeats devirtualizing optimizations more often
because I think it would but I haven't looked
probably would, though would a framework-y virtual dispath like that even get devirtualized much in the first place?
def not with a bunch of them in a heterogeneous collection, but I could see some other cases where it would
I'm biased ๐
but I think the raw ptrcast is probably not it, you'll want something to catch mismatches like this, so either a wrapper generator, or a userland fnPtrCast that knows how to check for "compatible" fn signatures
or just do the manual casting
oh look
at least in this silly example
it does not inhibit devirtualizing
// Type your code here, or load an example.
extern fn frob(i32) void;
extern fn freb(u8) void;
pub const VTable = struct {
blah: *const fn (*anyopaque, i32) void,
bleh: *const fn (*anyopaque, []const u8) void,
};
fn first_blah(a: *anyopaque, b: i32) void {
_ = a;
frob(b);
}
fn first_bleh(a: *anyopaque, b: []const u8) void {
...
wild
hell yeah
ok I worked from your thing and made a thing which works but I think really speaks to the value a @fnPtrCast builtin would provide: https://godbolt.org/z/bPdaGK684
and I probably got the const variance logic backwards
[EDIT]: better one https://godbolt.org/z/dq1PG6e5K
const std = @import("std");
// Type your code here, or load an example.
pub fn fnPtrCast(comptime DestFnType: type, src: anytype) *const DestFnType {
const SrcType = @TypeOf(src);
const dest_fn_typeinfo = @typeInfo(DestFnType).@"fn";
const src_typeinfo = @typeInfo(SrcType);
if (comptime SrcType == *const DestFnType or
s...
you dont need ptrCast to go from *T to *anyopaque btw
My solution would be to not use dynamic dispatch at all ๐ซ
true that less dynamic dispatch is usually simpler
just need to be judicious about it
considering this is a game, and all the behaviours etc are known before hand. Dynamic dispatch doesn't make much sense, unless you really have to squeeze on binary size