Skip to content

Commit

Permalink
Performance|Bindings|Client: Minor optimizations in input bindings
Browse files Browse the repository at this point in the history
Using compiled records for storing the bindings to avoid repeated
Record lookups.
  • Loading branch information
skyjake committed Nov 5, 2016
1 parent 8963328 commit 0c85254
Show file tree
Hide file tree
Showing 13 changed files with 212 additions and 132 deletions.
4 changes: 1 addition & 3 deletions doomsday/apps/client/include/ui/b_util.h
Expand Up @@ -56,9 +56,7 @@ bool B_CheckAxisPosition(Binding::ControlTest test, float testPos, float pos);
* @param localNum Local player number.
* @param context Relevant binding context, if any (may be @c nullptr).
*/
bool B_CheckCondition(de::Record const *cond, int localNum, BindContext const *context);

bool B_EqualConditions(de::Record const &a, de::Record const &b);
bool B_CheckCondition(Binding::CompiledConditionRecord const *cond, int localNum, BindContext const *context);

// ---------------------------------------------------------------------------------

Expand Down
6 changes: 4 additions & 2 deletions doomsday/apps/client/include/ui/bindcontext.h
Expand Up @@ -161,9 +161,11 @@ class BindContext
*
* @param localPlayer (@c < 0 || >= DDMAXPLAYERS) for all local players.
*/
de::LoopResult forAllImpulseBindings(int localPlayer, std::function<de::LoopResult (de::Record &)> func) const;
de::LoopResult forAllImpulseBindings(int localPlayer,
std::function<de::LoopResult (CompiledImpulseBindingRecord &)> func) const;

inline de::LoopResult forAllImpulseBindings(std::function<de::LoopResult (de::Record &)> func) const {
inline de::LoopResult forAllImpulseBindings(
std::function<de::LoopResult (CompiledImpulseBindingRecord &)> func) const {
return forAllImpulseBindings(-1/*all local players*/, func);
}

Expand Down
25 changes: 21 additions & 4 deletions doomsday/apps/client/include/ui/binding.h
Expand Up @@ -21,7 +21,7 @@
#define CLIENT_INPUTSYSTEM_BINDING_H

#include <de/Error>
#include <de/RecordAccessor>
#include <de/CompiledRecord>

/**
* Base class for binding record accessors.
Expand Down Expand Up @@ -62,6 +62,23 @@ class Binding : public de::RecordAccessor
ButtonStateUp
};

struct CompiledCondition
{
ConditionType type = Invalid;
ControlTest test = None;
int device = -1;
int id = -1;
float pos = 0;
bool negate = false;
bool multiplayer = false;

CompiledCondition() {}
CompiledCondition(de::Record const &rec);
bool operator == (CompiledCondition const &other) const;
};

typedef de::CompiledRecordT<CompiledCondition> CompiledConditionRecord;

public:
Binding() : RecordAccessor(0) {}
Binding(Binding const &other) : RecordAccessor(other) {}
Expand Down Expand Up @@ -97,11 +114,11 @@ class Binding : public de::RecordAccessor
*/
virtual de::String composeDescriptor() = 0;

de::Record &addCondition();
CompiledConditionRecord &addCondition();
int conditionCount() const;
bool hasCondition(int index) const;
de::Record &condition(int index);
de::Record const &condition(int index) const;
CompiledConditionRecord &condition(int index);
CompiledConditionRecord const &condition(int index) const;

/**
* Compare the binding conditions with @a other and return @c true if equivalent.
Expand Down
22 changes: 22 additions & 0 deletions doomsday/apps/client/include/ui/impulsebinding.h
Expand Up @@ -21,6 +21,7 @@
#define CLIENT_INPUTSYSTEM_IMPULSEBINDING_H

#include <de/String>
#include <de/CompiledRecord>
#include "Binding"
#include "ddevent.h"

Expand All @@ -39,6 +40,23 @@ enum ibcontroltype_t
#define IBDF_INVERSE 0x1
#define IBDF_TIME_STAGED 0x2

struct CompiledImpulseBinding
{
int id = -1;
int deviceId = -1;
int controlId = -1;
ibcontroltype_t type = IBD_TOGGLE; ///< Type of event.
float angle = 0;
int flags = 0;
int impulseId = 0; ///< Identifier of the bound player impulse.
int localPlayer = -1; ///< Local player number.

CompiledImpulseBinding() {}
CompiledImpulseBinding(de::Record const &bind);
};

typedef de::CompiledRecordT<CompiledImpulseBinding> CompiledImpulseBindingRecord;

/**
* Utility for handling input-device-control => impulse binding records.
*
Expand All @@ -54,9 +72,13 @@ class ImpulseBinding : public Binding

ImpulseBinding &operator = (de::Record const *d) {
*static_cast<Binding *>(this) = d;
def().resetCompiled();
return *this;
}

CompiledImpulseBindingRecord &def();
CompiledImpulseBindingRecord const &def() const;

void resetToDefaults();

de::String composeDescriptor();
Expand Down
6 changes: 2 additions & 4 deletions doomsday/apps/client/src/client/clientsubsector.cpp
Expand Up @@ -175,13 +175,11 @@ DENG2_PIMPL(ClientSubsector)

DecoratedSurface() {}

~DecoratedSurface()
{
~DecoratedSurface() {
qDeleteAll(decorations);
}

void markForUpdate(bool yes = true)
{
void markForUpdate(bool yes = true) {
if (::ddMapSetup) return;
needUpdate = yes;
}
Expand Down
4 changes: 2 additions & 2 deletions doomsday/apps/client/src/con_config.cpp
Expand Up @@ -233,10 +233,10 @@ static bool writeBindingsState(Path const &filePath)
});

// Impulses.
context.forAllImpulseBindings([&out, &context] (Record &rec)
context.forAllImpulseBindings([&out, &context] (CompiledImpulseBindingRecord &rec)
{
ImpulseBinding bind(rec);
PlayerImpulse const *impulse = P_PlayerImpulsePtr(bind.geti("impulseId"));
PlayerImpulse const *impulse = P_PlayerImpulsePtr(rec.compiled().impulseId);
DENG2_ASSERT(impulse);

out.writeText(String::format("bindcontrol local%i-%s \"%s\"\n",
Expand Down
83 changes: 38 additions & 45 deletions doomsday/apps/client/src/ui/b_util.cpp
Expand Up @@ -426,39 +426,42 @@ bool B_CheckAxisPosition(Binding::ControlTest test, float testPos, float pos)
return false;
}

bool B_CheckCondition(Record const *cond, int localNum, BindContext const *context)
bool B_CheckCondition(Binding::CompiledConditionRecord const *condRec, int localNum,
BindContext const *context)
{
DENG2_ASSERT(cond);
bool const fulfilled = !cond->getb("negate");
DENG2_ASSERT(condRec);

switch (cond->geti("type"))
auto const &cond = condRec->compiled();
bool const fulfilled = !cond.negate;

switch (cond.type)
{
case Binding::GlobalState:
if (cond->getb("multiplayer") && netGame)
if (cond.multiplayer && netGame)
return fulfilled;
break;

case Binding::AxisState: {
AxisInputControl const &axis = InputSystem::get().device(cond->geti("device")).axis(cond->geti("id"));
if (B_CheckAxisPosition(Binding::ControlTest(cond->geti("test")), cond->getf("pos"), axis.position()))
AxisInputControl const &axis = InputSystem::get().device(cond.device).axis(cond.id);
if (B_CheckAxisPosition(cond.test, cond.pos, axis.position()))
{
return fulfilled;
}
break; }

case Binding::ButtonState: {
ButtonInputControl const &button = InputSystem::get().device(cond->geti("device")).button(cond->geti("id"));
ButtonInputControl const &button = InputSystem::get().device(cond.device).button(cond.id);
bool isDown = button.isDown();
if (( isDown && cond->geti("test") == Binding::ButtonStateDown) ||
(!isDown && cond->geti("test") == Binding::ButtonStateUp))
if (( isDown && cond.test == Binding::ButtonStateDown) ||
(!isDown && cond.test == Binding::ButtonStateUp))
{
return fulfilled;
}
break; }

case Binding::HatState: {
HatInputControl const &hat = InputSystem::get().device(cond->geti("device")).hat(cond->geti("id"));
if (hat.position() == cond->getf("pos"))
HatInputControl const &hat = InputSystem::get().device(cond.device).hat(cond.id);
if (hat.position() == cond.pos)
{
return fulfilled;
}
Expand All @@ -469,32 +472,21 @@ bool B_CheckCondition(Record const *cond, int localNum, BindContext const *conte
{
// Evaluate the current state of the modifier (in this context).
float pos = 0, relative = 0;
B_EvaluateImpulseBindings(context, localNum, cond->geti("id"), &pos, &relative, false /*no triggered*/);
if ((cond->geti("test") == Binding::ButtonStateDown && fabs(pos) > .5) ||
(cond->geti("test") == Binding::ButtonStateUp && fabs(pos) < .5))
B_EvaluateImpulseBindings(context, localNum, cond.id, &pos, &relative, false /*no triggered*/);
if ((cond.test == Binding::ButtonStateDown && fabs(pos) > .5) ||
(cond.test == Binding::ButtonStateUp && fabs(pos) < .5))
{
return fulfilled;
}
}
break;

default: DENG2_ASSERT(!"B_CheckCondition: Unknown cond->type"); break;
default: DENG2_ASSERT(!"B_CheckCondition: Unknown cond.type"); break;
}

return !fulfilled;
}

bool B_EqualConditions(Record const &a, Record const &b)
{
return (a.geti("type") == b.geti("type") &&
a.geti("test") == b.geti("test") &&
a.geti("device") == b.geti("device") &&
a.geti("id") == b.geti("id") &&
de::fequal(a.getf("pos"), b.getf("pos")) &&
a.getb("negate") == b.getb("negate") &&
a.getb("multiplayer") == b.getb("multiplayer"));
}

/// @todo: Belongs in BindContext? -ds
void B_EvaluateImpulseBindings(BindContext const *context, int localNum, int impulseId,
float *pos, float *relativeOffset, bool allowTriggered)
Expand All @@ -511,18 +503,20 @@ void B_EvaluateImpulseBindings(BindContext const *context, int localNum, int imp
bool conflicted[NUM_IBD_TYPES]; de::zap(conflicted);
bool appliedState[NUM_IBD_TYPES]; de::zap(appliedState);

context->forAllImpulseBindings(localNum, [&] (Record &rec)
context->forAllImpulseBindings(localNum, [&] (CompiledImpulseBindingRecord &rec)
{
// Wrong impulse?
ImpulseBinding bind(rec);
if (bind.geti(QStringLiteral("impulseId")) != impulseId) return LoopContinue;
//ImpulseBinding bind(rec);
auto const &bind = rec.compiled();
if (bind.impulseId != impulseId) return LoopContinue;

// If the binding has conditions, they may prevent using it.
bool skip = false;
ArrayValue const &conds = bind.geta(QStringLiteral("condition"));
ArrayValue const &conds = rec.geta(QStringLiteral("condition"));
DENG2_FOR_EACH_CONST(ArrayValue::Elements, i, conds.elements())
{
if (!B_CheckCondition((*i)->as<RecordValue>().record(), localNum, context))
if (!B_CheckCondition(static_cast<Binding::CompiledConditionRecord *>
((*i)->as<RecordValue>().record()), localNum, context))
{
skip = true;
break;
Expand All @@ -531,17 +525,17 @@ void B_EvaluateImpulseBindings(BindContext const *context, int localNum, int imp
if (skip) return LoopContinue;

// Get the device.
InputDevice const *device = InputSystem::get().devicePtr(bind.geti(QStringLiteral("deviceId")));
InputDevice const *device = InputSystem::get().devicePtr(bind.deviceId);
if (!device || !device->isActive())
return LoopContinue; // Not available.

// Get the control.
InputControl *ctrl = nullptr;
switch (bind.geti("type"))
switch (bind.type)
{
case IBD_AXIS: ctrl = &device->axis (bind.geti(QStringLiteral("controlId"))); break;
case IBD_TOGGLE: ctrl = &device->button(bind.geti(QStringLiteral("controlId"))); break;
case IBD_ANGLE: ctrl = &device->hat (bind.geti(QStringLiteral("controlId"))); break;
case IBD_AXIS: ctrl = &device->axis (bind.controlId); break;
case IBD_TOGGLE: ctrl = &device->button(bind.controlId); break;
case IBD_ANGLE: ctrl = &device->hat (bind.controlId); break;

default: DENG2_ASSERT(!"B_EvaluateImpulseBindings: Invalid bind.type"); break;
}
Expand All @@ -554,7 +548,8 @@ void B_EvaluateImpulseBindings(BindContext const *context, int localNum, int imp
{
if (context && axis->bindContext() != context)
{
if (axis->hasBindContext() && !axis->bindContext()->findImpulseBinding(bind.geti(QStringLiteral("deviceId")), IBD_AXIS, bind.geti(QStringLiteral("controlId"))))
if (axis->hasBindContext() && !axis->bindContext()->
findImpulseBinding(bind.deviceId, IBD_AXIS, bind.controlId))
{
// The overriding context doesn't bind to the axis, though.
if (axis->type() == AxisInputControl::Pointer)
Expand Down Expand Up @@ -605,12 +600,12 @@ void B_EvaluateImpulseBindings(BindContext const *context, int localNum, int imp
// Expired?
if (!(hat->bindContextAssociation() & InputControl::Expired))
{
devicePos = (hat->position() == bind.getf(QStringLiteral("angle"))? 1.0f : 0.0f);
devicePos = (fequal(hat->position(), bind.angle)? 1.0f : 0.0f);
deviceTime = hat->time();
}
}

int const bflags = bind.geti(QStringLiteral("flags"));
int const bflags = bind.flags;

// Apply further modifications based on flags.
if (bflags & IBDF_INVERSE)
Expand All @@ -632,16 +627,14 @@ void B_EvaluateImpulseBindings(BindContext const *context, int localNum, int imp
// Is this state contributing to the outcome?
if (!de::fequal(devicePos, 0.f))
{
int const btype = bind.geti(QStringLiteral("type"));

if (appliedState[btype])
if (appliedState[bind.type])
{
// Another binding already influenced this; we have a conflict.
conflicted[btype] = true;
conflicted[bind.type] = true;
}

// We've found one effective binding that influences this control.
appliedState[btype] = true;
appliedState[bind.type] = true;
}
return LoopContinue;
});
Expand Down

0 comments on commit 0c85254

Please sign in to comment.