Hello everyone!
I am trying to implement Command pattern. Briefly, I need to catch input event and produce a corresponding object instance.
For example, I would like to bind:
KeyWPressedEvent -> result = new(ref ForwardCommand) MouseMovedEvent -> result = new(ref RotationCommand)
So, in Python I would do something like this:
# Note here ForwardCommand and RotationCommand are types, not instances
bindings = {
KeyWID: ForwardCommand,
MouseID: RotationCommand,
}
# And here is how I would produce a Command object
event = poll_events()
command = bindings[getID(event)]()
Note that I instantiated a corresponding Command object by using braces.
So, going back to nim...
var bindings = initTable[EventID, typedesc] # Error, I cannot use typedesc here
var bindings = initTable[EventID, proc(): ref Command] # Lets create a Command factory here
bindings.add(KeyWID, proc(): ref Command = new(ref ForwardCommand))
bindings.add(MouseID, proc(): ref Command = new(ref RotationCommand))
let event = pollEvents()
var command = bindings[getID(event)]()
Here I instantiate command by calling corresponding factory. It works but looks ugly, and now I need to define separate Command factory for each binding.
Going further, I can create a template which will create a corresponding factory, like this:
type CommandFactory = proc(): ref Command
proc newFactory(T: typedesc): CommandFactory =
return proc(): ref Command = new(ref T)
bindings.add(KeyWID, newFactory(ForwardCommand))
The code now is simple, but what the hell I am doing here? I am calling a template to create a factory which creates a simple Command object. I bet you guys know a better way to implement such a binding system, and that's what I'm asking you to help me with :)
{"key1": "value1", "key2", "key3": "value2"}
# is the same as:
[("key1", "value1"), ("key2", "value2"), ("key3", "value2")]
You'll be fine with this
# BASE
type Command = ref object of RootObj
method execute*(c: Command) {.base.} = discard
# Greet
type CommandGreet = ref object of Command
method execute(c: CommandGreet) = echo "hi!"
# Exit
type CommandExit = ref object of Command
method execute(c: CommandExit) = quit "Bye Bye"
# Triggers
type Trigger = enum
tKEY_W
tMOUSE
# Table
var bindings: array[Trigger, Command] = [
tKEY_W: CommandGreet(),
tMOUSE: CommandExit(),
]
########
for t in {tKEY_W, tMOUSE}:
let command = bindings[t]
execute command
in action Wow, I've never seen this array literal/initialization syntax before, @Arrrrrrrrr:
var bindings: array[Trigger, Command] = [
tKEY_W: CommandGreet(),
tMOUSE: CommandExit(),
]
Can you (or anyone else) explain how this [key: val] syntax works? (and where it is documented?)
I can't find anything about it in the manual or tutorials... and yet, it compiles.
@Arrrrrrrrr, thanks, but I want to create new instance of CommandGreet/CommandExit each time the event occurs. According to your code, there is only one instance during runtime.
The cheatsheet does tell it all?
.. code-block:: nimrod
if x == "abc":
echo "xyz"
or
```nimrod
if x == "abc":
echo "xyz"```
Works well:
if x == "abc":
echo "xyz"
@c0ntribut0r
I think is this:
Remove the extra spaces between the ` characters.
I got it from here: http://nim-lang.org/rst.html look at Code blocks
echo("O_O Thnx everyone! Why it didn't work for me before? Probably I mistyped somewhere...")
I forgot to add newline before code-block
@jboy and @Arrrrrrrrr this array "constructor" is not really working and ignores the values of the index fields (before the colon).
type EinsZwei = enum
eins = 1
zwei = 2
var a: array[EinsZwei, string]= [7: "eins", 8:"zwei"]
echo a[zwei] # zwei
echo int(zwei) # 2
Hi @c0ntribut0r, the reason I asked to see the definition of KeyWID (and EventID) is because different code idioms will be available depending upon whether KeyWID is (for example) an enumeration (enum), a constant (const), etc.
If it's an enumeration, you can use it in an object variant (as I've mentioned) or a case statement.
Also, if it's an enumeration, it's relevant whether it's an ordinal type. For example, as @Arrrrrrrrr demonstrated, arrays can be indexed by ordinal types.
It's not entirely clear what your metric is for "better" -- whether you want the most idiomatic Nim code, the most unique-to-Nim code, the fastest executing Nim code, the fewest keystrokes, etc... but incorporating idioms is often a good way to accomplish at least a few of these goals.
I also wonder: Don't you need to give ForwardCommand & RotationCommand parameters when you instantiate the objects? (Unless your movement commands have fixed translation/rotation parameters...)
bindCommand(MouseMotionID, RotationCommand)
Under the hood bindProc is something like this:
# simpified code, I've cut everything redundant
proc bindCommand*(eventID: EventID, commandType: typedesc) =
bindings.add(eventID, proc(event: var Event): ref Command = new(ref commandType))
So, the problem was to store typedesc in my bindings table, and as far as I cannot store plain typedesc, I store procs that create new ref typedesc. I guess there is no better solution for my requirements.