I'm implementing an event system, but currently I'm stuck.
To illustrate my problem, I wrote this example:
proc greet(msg: string) =
echo msg
proc someotherproc() =
echo "Hallo Welt"
let procedures: seq[the_type_im_searching_for] = @[]
procedures.add(some_cast(greet))
procedures.add(some_cast(someotherproc))
cast_back(procedures[0])("Hallo Welt")
cast_back(procedures[1])()
I'm searching for the_type_im_searching_for and some_cast and cast_back. Simply using cast[int](greet) or cast[proc()](greet) and cast[proc(msg:string)](uniformtype) don't work.
I know this is unsafe but the whole mechanic will be hidden.
I actually found the solution by myself:
# Note: extremely unsafe code, assumes that this is running on a 64-bit machine
# 16 byte should be used for closures
var procedures: seq[array[8, byte]] = @[]
procedures.add(cast[array[8, byte]](greet))
procedures.add(cast[array[8, byte]](someotherproc))
# It does compile and work without the nimcall pragma, but I let it there, safety first
cast[proc(msg: string) {.nimcall.}](procedures[0])("Hallo Welt")
cast[proc(){.nimcall.}](procedures[1])()
Also procedures has to be a var instead of let, ah I'm so depending on auto linting.
Atleast I learned something while breaking Nim. Breaking the rules is something which is only rarely taught(for good reasons). But I'm glad that it's possible in Nim.
Why not use closures to hide the differences in parameters that the procs take?
proc takesIntAndString(param: int; paramB: string) = echo param, " ", paramB
proc makeCompatible(param: int; paramB: string): (proc ()) =
result = proc () =
takesIntAndString(param, paramB)
var procedures: seq[proc ()] = @[]
procedures.add makeCompatible(1, "abc")
Why not to use pointer instead of array[8, byte]. Will Work for both 64 and 32 bits.
Yes, of course this is much better
Why not use closures to hide the differences in parameters that the procs take?
The problem with this method is that I would have to write one macro for wrapping and one for unwrapping. Altough this would be the more correct way it's way more difficult.
@doofenstein: shameless plug, you might be interested in Nimoy
I know Nimoy, but it isn't suitable for my project because it creates threads in the background. Also it has the same problem as almost every event system I saw, it requires to wrap the event args into objects. That's the reason I'm doing this. I want to implement an event system similar to this one: https://luxeengine.com/guide/#events
I think I stick with casting to a pointer and back. Is it possible to check with is if a (generic) procedure variable is a closure or not? Otherwise I would have to restrict it to non closures.
I know Nimoy, but it isn't suitable for my project because it creates threads in the background.
actually it uses an abstract Executor which as a default implementation happens to uses threads :-)
Also it has the same problem as almost every event system I saw, it requires to wrap the event args into objects. That's the reason I'm doing this. I want to implement an event system similar to this one: https://luxeengine.com/guide/#events
well you can send an actor any type A, so if you want to send pointers, you're free to do so. The real pain point is that I'm still using channels to send messages, so I guess a deep copy is probably going to happen if you use managed refs
still interested to see where your effort will be going, though :-)
After playing a bit around, I think Araqs solution is the better one ^^. But now I need a way to get the types of the arguments of a procvar in a macro. Is this possible?
EDIT: Yes, it is(+ fixed plural):
macro listen(self: EventSystem, event: string, handler: proc): untyped =
echo treeRepr(handler.getType)
when isMainModule:
let x = 0
var closure = proc(msg: string, x, y: int) = echo x
echo name(type(closure))
listen(nil, "test", closure)
So that's what I've come up with: https://gist.github.com/RSDuck/e6969f0331d3bc9dd4fd6666e2bebad3
Example:
testSystem.listen("ui.*.click") do (event: string, args: openArray[Variant]):
echo "Someone clicked on something"
if event == "ui.player.click":
echo "To be precise, ", args[0].stringVal, " clicked on something"
testSystem.listen("ui.player.*") do (event: string, name: string, clickedOnId: int):
echo "Player UI event!"
testSystem.fire("ui.player.click", "Luke Skywalker", 12345)
It's pretty much a hybrid between the event system from the game engines Luxe and Urho3D but I managed to get rid of the need to wrap the arguments into an wrapper object
I'm worried about it's performance. On my PC the unoptimzed build manages to fire 320 000 events in 1.5 seconds. So if a game runs with 60 FPS theoretically only ~3555 events could be fired. Nevertheless it is much faster than Luxes implementation which uses Regexes and a linear search.
On my PC the unoptimzed build manages to fire 320 000 events in 1.5 seconds. So if a game runs with 60 FPS theoretically only ~3555 events could be fired. Nevertheless it is much faster than Luxes implementation which uses Regexes and a linear search.
I wouldn't worry. A factor of 10 between debug and release builds in Nim is not uncommon. Nim's own callstack emulation for convenient stack traces is costly and can also be disabled selectively via --stackTrace:off.