So I have two main threads in my game. One managing the window and rendering, and another thread doing all the logic.
The logic thread is then filling out state buffers with relevant bits of the world data that the render thread can then render (interpolating between the two most recent buffers). The window thread forwards the keyboard and mouse input events to the logic thread to keep player movement and such simple.
My current idea is to follow the same system as the main game world, and keep all interactions on the logic thread, while telling the render thread what it needs to render. However a solution that lets all the ingame UI stuff not care about threads so much would be nice.
I am also thinking about breaking some of the logic stuff out more (e.g. clickedOnSlot and the functions it works with) to help with working on multiplayer, but that still seems to leave me with this problem where that stuff is on a different thread to the actual rendering, even if I moved the UI's user input events back to the render thread...
/**@brief Abstract class for interacting with and rendering an inventory.*/
class BaseInventoryUi
{
public:
/**Will be constructed on the logic thread when the player does
* something that needs a UI.
*/
BaseInventoryUi(
grf::HudRenderer &renderer,
Inventory &inventory);
virtual ~BaseInventoryUi(){}
/**Must be called on the render thread!*/
void init();
void render(int stateIndex);
/**Must be called by the logic thread since it access the inventory
* reference.
*/
void logicUpdate(int stateIndex);
/**Must be called on the logic thread!*/
void onMouseDown(const MouseEvent &evt);
/**Called by render thread to handle tool tips*/
ItemStack getRenderStackAt(int x, int y, int stateIndex)const;
protected:
Inventory &inventory;
const unsigned size;
virtual Vector2I getSlotPos(unsigned slot)const=0;
virtual int getSlotAt(int x, int y)const=0;
virtual ItemStack getHeldStack()const=0;
virtual void setHeldStack(ItemStack itemStack)=0;
/**In UI's screens with 2 inventories, move this stack to the other
* inventory.
* @return The number of units that could not be moved.
*/
virtual uint16_t moveToOtherInventory(unsigned from, ItemStack itemStack)=0;
private:
ItemSlotsRenderer itemSlotsRenderer;
//A better way?
struct State
{
Inventory inventory;
};
State states[3];
/**Must be called on the logic thread!
* - Shift click to move stack to other inventory
* - Left click to place held stack, and pick up existing stack if different
* - Right click when holding nothing to pick up half the stack
* - Right click when holding something to place 1
*
* Calls:
* - Any methods using the this->inventory reference
* - getHeldStack
* - setHeldStack
* - moveToOtherInventory
*/
void clickedOnSlot(
unsigned slot,
bool left,
bool shift);
};
#include "Precompiled.hpp"
#include "BaseInventoryUi.hpp"
#include "../ItemStack.hpp"
#include "../Items.hpp"
namespace game
{
BaseInventoryUi::BaseInventoryUi(
grf::HudRenderer &renderer,
Inventory &inventory)
: inventory(inventory)
, size((unsigned)inventory.getSize())
, itemSlotsRenderer(renderer)
{
}
void BaseInventoryUi::init()
{
std::vector<Vector2I> slots;
for (unsigned i = 0; i < size; ++i)
{
slots.push_back(getSlotPos(i));
}
itemSlotsRenderer.init(slots);
}
void BaseInventoryUi::render(int stateIndex)
{
const auto& state = states[stateIndex];
itemSlotsRenderer.renderItems(state.inventory);
}
void BaseInventoryUi::logicUpdate(int stateIndex)
{
states[stateIndex].inventory = inventory;
}
void BaseInventoryUi::onMouseDown(const MouseEvent &evt)
{
int slot = getSlotAt(evt.pos.x, evt.pos.y);
if (slot < 0) return;
//TODO: Multiplayer?
clickedOnSlot((unsigned)slot, evt.button == VK_LBUTTON, evt.shiftDown);
}
void BaseInventoryUi::clickedOnSlot(
unsigned slot,
bool left,
bool shift)
{
...logic...
}
}