-
-
Save dwilliamson/1d5e811f81c5456d8eaf0ed62982a6cc to your computer and use it in GitHub Desktop.
// LIB | |
using Buffer = std::vector<uint8_t>; | |
// LIB | |
struct Command | |
{ | |
int size; | |
// NOTE: This *does not* need to be virtual - it's just the way std::function achieves it... | |
virtual void Call() = 0; | |
}; | |
// USER | |
void AddCommandA(Buffer& buffer, int x, int y, int z) | |
{ | |
buffer << [x, y, z] | |
{ | |
DoSomethingWith(x, y, z); | |
}; | |
} | |
// LIB | |
void ExecCommands(const Buffer& buffer) | |
{ | |
const uint8_t* start = buffer.data(); | |
const uint8_t* end = start + buffer.size(); | |
while (start < end) | |
{ | |
Command* command = (Command*)start; | |
command->Call(); | |
start += command->size; | |
} | |
} |
// Anything marked `USER` is something the user has to write and maintain. | |
// Anything marked `LIB` is in the library provided by the command buffer authors. | |
// USER | |
enum class CommandID | |
{ | |
A, B, C, ... | |
}; | |
// LIB | |
struct Command | |
{ | |
Command() = default; | |
Command(CommandID id) : id(id) { } | |
CommandID id; | |
}; | |
// USER | |
struct CommandAParams : public Command | |
{ | |
static const CommandID ID = CommandID::A; | |
CommandAParams() : Command(ID) { } | |
int x, y, z; | |
}; | |
// LIB | |
using Buffer = std::vector<uint8_t>; | |
// USER | |
void AddCommandA(Buffer& buffer, int x, int y, int z) | |
{ | |
CommandAParams params; | |
params.x = x; | |
params.y = y; | |
params.z = z; | |
AddCommand(buffer, params); | |
} | |
// USER | |
void ExecCommands(const Buffer& buffer) | |
{ | |
const uint8_t* start = buffer.data(); | |
const uint8_t* end = start + buffer.size(); | |
while (start < end) | |
{ | |
CommandID id = (CommandID)*start; | |
switch (id) | |
{ | |
case CommandID::A: | |
CommandAParams params = *(CommandAParams*)start; | |
DoSomethingWith(params.x, params.y, params.z); | |
start += sizeof(params); | |
break; | |
case CommandID::B: | |
... | |
} | |
} | |
} |
It's something like:
using Buffer = std::vector<std::function>;
template <class Lambda>
Buffer& operator << (Buffer& buffer, Lambda&& lambda)
{
buffer.push_back(std::forward(lambda));
return buffer;
}
But in reality Buffer
is my own thread-safe queue and I have my own implementation of std::function
that allocates all needed memory from within the queue, leading to no dynamic allocations.
Thanks for your reply. I tried to write a demo based on your snippets, but still can not get it run. Could you provide a minimal demo for me to get the whole idea? Thanks very much!
Ha, I guess I have figured it out. Thanks!
#include <functional>
#include <iostream>
#include <vector>
using Commands = std::vector<std::function<int()>>;
template <class Lambda>
void operator<<(Commands &cmds, Lambda lambda) {
cmds.push_back(lambda);
}
void AddA(Commands &cmds, int a, int b) {
cmds << [a, b]() -> int {
std::cout << a << "\n";
return a + b;
};
}
int main(int argc, char *argv[]) {
Commands cmds;
AddA(cmds, 1, 2);
AddA(cmds, 2, 2);
AddA(cmds, 3, 2);
for (const auto &f : cmds) {
f();
}
return 0;
}
Pretty much! Though note your lambda syntax can be simpler:
cmds << [a, b] {
std::cout << a << "\n";
return a + b;
};
My rule of thumb is: never bind [=]
, [&]
, [this]
or pointers/references. You don't want the command to outlive the lifetime of the object you capture. Any references to objects go with an ID that's looked up inside the lambda; the C++ equivalent of this is capturing a smart pointer.
Hi, I am not sure how the following codes work, could you explain ?
Thanks!