Skip to main content
Code Review

Return to Question

added 59 characters in body; edited tags
Source Link
G. Sliepen
  • 69k
  • 3
  • 74
  • 180

I started reading the GameProgrammingPatterns book and wanted to implement the Command patterncommand pattern.

I started reading the GameProgrammingPatterns book and wanted to implement the Command pattern.

I started reading the GameProgrammingPatterns book and wanted to implement the command pattern.

Became Hot Network Question
edited tags
Source Link
toolic
  • 15.2k
  • 5
  • 29
  • 213

StartedI started reading the GameProgrammingPatterns book and wanted to implement the Command pattern.

Basically to create a command, as mentioned you shall use make_command templated method, or itsit's your responsibilyresponsibility to create a context and a method yourself. Its gonnaIt's going to be a little overhead for input handler for instance to store the commands, but there iswill always gonna be one input handler in the whole game... but many commands. What do you think? Is this design good? Or are there any other ways to do this, without virtual overhead, lambda captures and std::function?std::function?

Started reading the GameProgrammingPatterns book and wanted to implement the Command pattern.

Basically to create a command, as mentioned you shall use make_command templated method, or its your responsibily to create a context and a method yourself. Its gonna be a little overhead for input handler for instance to store the commands, but there is always gonna be one input handler in the whole game... but many commands. What do you think? Is this design good? Or are there any other ways to do this, without virtual overhead, lambda captures and std::function??

I started reading the GameProgrammingPatterns book and wanted to implement the Command pattern.

Basically to create a command, as mentioned you shall use make_command templated method, or it's your responsibility to create a context and a method yourself. It's going to be a little overhead for input handler for instance to store the commands, but there will always be one input handler in the whole game... but many commands. What do you think? Is this design good? Or are there any other ways to do this, without virtual overhead, lambda captures and std::function?

Source Link

Game Design Patterns Command implementation

Started reading the GameProgrammingPatterns book and wanted to implement the Command pattern.

namespace bebop::patterns
{
struct Command
{
 using Fn = void (*)(void*); // Command return type is void
 Fn exec; // The function to be executed when execute() is called
 void* m_ctx; // The context for the function
 void execute() const { exec(m_ctx); }
};
template <typename T, typename Method, typename... Args>
struct CommandImpl
{
 CommandImpl(T* obj, Method method, Args&&... args)
 : m_ctx{obj, method, std::make_tuple(std::forward<Args>(args)...)}
 {
 m_command = Command{&trampoline, &m_ctx};
 }
 void execute() const { m_command.execute(); }
 Command command() const { return m_command; }
 private:
 struct Context
 {
 T* obj;
 Method method;
 std::tuple<Args...> args;
 };
 Context m_ctx;
 static void trampoline(void* raw)
 {
 auto* m_ctx = static_cast<Context*>(raw);
 std::apply(
 [m_ctx](auto&&... unpacked_args)
 {
 (m_ctx->obj->*m_ctx->method)(
 std::forward<decltype(unpacked_args)>(unpacked_args)...);
 },
 m_ctx->args);
 }
 Command m_command;
};
// Deduction guide
template <typename T, typename Method, typename... Args>
auto make_command(T* obj, Method method, Args&&... args)
{
 return CommandImpl<T, Method, std::decay_t<Args>...>(
 obj, method, std::forward<Args>(args)...);
}
} // namespace bebop::patterns

Decided to implement without virtual functions because we all know that virtual functions add runtime overhead.std::function is adding overhead too, so the only right way to statically do this, without even heap overhead is this.

  • To generalize the command function we shall have a function pointer, usually when executing a command action we are not interested in the return result, but the command action function may consume some parameters. So to generalize it we shall have a void(*)(void*) function pointer.
  • To have somehow any context about the function we must also store a void* ctx to store the context of the function somewhere.

Now, its gonna be an overhead for an engineer to create the command pointer, the context to create command. So to have some sugar coding lets add a template make_command which receives the pointer to the object, the action of the object, and the parameters that action shall take. But to implement this, we cannot use any captures , std::function or etc... So we must somehow write a trampoline for our case. Thats why templated CommandImpl is used, to create a right command with the trampoline method and right context(with the arguments injected as a tuple). And the templated make_command just returns that implementation.

The usage:

#include <iostream>
#include <memory>
#include <unordered_map>
#include <utility>
#include "Command.hpp"
struct Player
{
 void moveUp() { std::cout << "UP\n"; }
 void moveDown() { std::cout << "DOWN\n"; }
 void moveRight() { std::cout << "RIGHT\n"; }
 void moveLeft() { std::cout << "LEFT\n"; }
 void shoot(int n) { std::cout << "Shoot " << n << "\n"; }
};
struct InputHandler
{
 void bind(char c, bebop::patterns::Command cmd) { bindings[c] = cmd; }
 bebop::patterns::Command* getCommand(char c)
 {
 auto it = bindings.find(c);
 return it != bindings.end() ? &it->second : nullptr;
 }
 template <typename CmdImpl>
 void bindOwned(char c, CmdImpl&& impl)
 {
 using ImplType = std::decay_t<CmdImpl>;
 auto implPtr = std::make_shared<ImplType>(std::forward<CmdImpl>(impl));
 // Extract the Command before erasing type
 bebop::patterns::Command cmd = implPtr->command();
 // Store lifetime-managed pointer
 storage.emplace_back(std::move(implPtr));
 // Bind command (safe — the context pointer is still valid inside
 // CommandImpl)
 bind(c, cmd);
 }
 private:
 std::unordered_map<char, bebop::patterns::Command> bindings;
 std::vector<std::shared_ptr<void>> storage; // type-erased ownership
};
int main()
{
 Player player;
 InputHandler handler;
 handler.bindOwned('w',
 bebop::patterns::make_command(&player, &Player::moveUp));
 handler.bindOwned(
 'v', bebop::patterns::make_command(&player, &Player::shoot, 5));
 auto m_command = bebop::patterns::make_command(&player, &Player::shoot, 10);
 m_command.execute();
 std::string s;
 while (std::cin >> s)
 {
 if (s.size() != 1)
 {
 continue;
 }
 if (auto* cmd = handler.getCommand(s[0]))
 cmd->execute();
 }
 return 0;
}

Basically to create a command, as mentioned you shall use make_command templated method, or its your responsibily to create a context and a method yourself. Its gonna be a little overhead for input handler for instance to store the commands, but there is always gonna be one input handler in the whole game... but many commands. What do you think? Is this design good? Or are there any other ways to do this, without virtual overhead, lambda captures and std::function??

lang-cpp

AltStyle によって変換されたページ (->オリジナル) /