mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-06-17 18:48:47 +02:00
merging and fixing the POse transmission bug
This commit is contained in:
commit
feeb4b68a6
14 changed files with 329 additions and 192 deletions
|
@ -1,22 +1,25 @@
|
||||||
{
|
{
|
||||||
"name": "Keyboard/Mouse to Actions",
|
"name": "Keyboard/Mouse to Actions",
|
||||||
"channels": [
|
"channels": [
|
||||||
{ "from": "Keyboard.W", "to": "Actions.LONGITUDINAL_FORWARD" },
|
|
||||||
{ "from": "Keyboard.S", "to": "Actions.LONGITUDINAL_BACKWARD" },
|
|
||||||
{ "from": "Keyboard.A", "to": "Actions.YAW_LEFT" },
|
|
||||||
{ "from": "Keyboard.D", "to": "Actions.YAW_RIGHT" },
|
|
||||||
{ "from": "Keyboard.A", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" },
|
{ "from": "Keyboard.A", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" },
|
||||||
{ "from": "Keyboard.D", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" },
|
{ "from": "Keyboard.D", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" },
|
||||||
{ "from": "Keyboard.A", "when": "Keyboard.RightMouseClick", "to": "Actions.LATERAL_LEFT" },
|
{ "from": "Keyboard.A", "when": "Keyboard.RightMouseClick", "to": "Actions.LATERAL_LEFT" },
|
||||||
{ "from": "Keyboard.D", "when": "Keyboard.RightMouseClick", "to": "Actions.LATERAL_RIGHT" },
|
{ "from": "Keyboard.D", "when": "Keyboard.RightMouseClick", "to": "Actions.LATERAL_RIGHT" },
|
||||||
|
|
||||||
|
{ "from": "Keyboard.W", "to": "Actions.LONGITUDINAL_FORWARD" },
|
||||||
|
{ "from": "Keyboard.S", "to": "Actions.LONGITUDINAL_BACKWARD" },
|
||||||
|
{ "from": "Keyboard.A", "to": "Actions.YAW_LEFT" },
|
||||||
|
{ "from": "Keyboard.D", "to": "Actions.YAW_RIGHT" },
|
||||||
{ "from": "Keyboard.C", "to": "Actions.VERTICAL_DOWN" },
|
{ "from": "Keyboard.C", "to": "Actions.VERTICAL_DOWN" },
|
||||||
{ "from": "Keyboard.E", "to": "Actions.VERTICAL_UP" },
|
{ "from": "Keyboard.E", "to": "Actions.VERTICAL_UP" },
|
||||||
|
|
||||||
{ "from": "Keyboard.Up", "to": "Actions.LONGITUDINAL_FORWARD" },
|
{ "from": "Keyboard.Up", "to": "Actions.LONGITUDINAL_FORWARD" },
|
||||||
{ "from": "Keyboard.Down", "to": "Actions.LONGITUDINAL_BACKWARD" },
|
{ "from": "Keyboard.Down", "to": "Actions.LONGITUDINAL_BACKWARD" },
|
||||||
{ "from": "Keyboard.Left", "to": "Actions.YAW_LEFT" },
|
{ "from": "Keyboard.Left", "to": "Actions.YAW_LEFT" },
|
||||||
{ "from": "Keyboard.Right", "to": "Actions.YAW_RIGHT" },
|
{ "from": "Keyboard.Right", "to": "Actions.YAW_RIGHT" },
|
||||||
{ "from": "Keyboard.PgDown", "to": "Actions.VERTICAL_DOWN" },
|
{ "from": "Keyboard.PgDown", "to": "Actions.VERTICAL_DOWN" },
|
||||||
{ "from": "Keyboard.PgUp", "to": "Actions.VERTICAL_UP" },
|
{ "from": "Keyboard.PgUp", "to": "Actions.VERTICAL_UP" },
|
||||||
|
|
||||||
{ "from": "Keyboard.MouseMoveLeft", "when": "Keyboard.RightMouseClick", "to": "Actions.YAW_LEFT" },
|
{ "from": "Keyboard.MouseMoveLeft", "when": "Keyboard.RightMouseClick", "to": "Actions.YAW_LEFT" },
|
||||||
{ "from": "Keyboard.MouseMoveRight", "when": "Keyboard.RightMouseClick", "to": "Actions.YAW_RIGHT" },
|
{ "from": "Keyboard.MouseMoveRight", "when": "Keyboard.RightMouseClick", "to": "Actions.YAW_RIGHT" },
|
||||||
{ "from": "Keyboard.MouseMoveUp", "when": "Keyboard.RightMouseClick", "to": "Actions.PITCH_UP" },
|
{ "from": "Keyboard.MouseMoveUp", "when": "Keyboard.RightMouseClick", "to": "Actions.PITCH_UP" },
|
||||||
|
|
|
@ -629,12 +629,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
||||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||||
connect(userInputMapper.data(), &UserInputMapper::actionEvent, _controllerScriptingInterface, &ControllerScriptingInterface::actionEvent);
|
connect(userInputMapper.data(), &UserInputMapper::actionEvent, _controllerScriptingInterface, &ControllerScriptingInterface::actionEvent);
|
||||||
connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) {
|
connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) {
|
||||||
if (state) {
|
if (state && action == toInt(controller::Action::TOGGLE_MUTE)) {
|
||||||
switch (controller::Action(action)) {
|
|
||||||
case controller::Action::TOGGLE_MUTE:
|
|
||||||
DependencyManager::get<AudioClient>()->toggleMute();
|
DependencyManager::get<AudioClient>()->toggleMute();
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
31
libraries/controllers/src/controllers/Conditional.cpp
Normal file
31
libraries/controllers/src/controllers/Conditional.cpp
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis 2015/10/20
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Conditional.h"
|
||||||
|
|
||||||
|
#include <QtCore/QJsonValue>
|
||||||
|
|
||||||
|
#include "Endpoint.h"
|
||||||
|
|
||||||
|
namespace controller {
|
||||||
|
|
||||||
|
Conditional::Pointer Conditional::parse(const QJsonValue& json) {
|
||||||
|
return Conditional::Pointer();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EndpointConditional::satisfied() {
|
||||||
|
if (!_endpoint) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto value = _endpoint->value();
|
||||||
|
if (value == 0.0f) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
63
libraries/controllers/src/controllers/Conditional.h
Normal file
63
libraries/controllers/src/controllers/Conditional.h
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis 2015/10/20
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#ifndef hifi_Controllers_Conditional_h
|
||||||
|
#define hifi_Controllers_Conditional_h
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <QtCore/QString>
|
||||||
|
|
||||||
|
#include <shared/Factory.h>
|
||||||
|
|
||||||
|
#include "Endpoint.h"
|
||||||
|
|
||||||
|
class QJsonValue;
|
||||||
|
|
||||||
|
namespace controller {
|
||||||
|
/*
|
||||||
|
* encapsulates a source, destination and filters to apply
|
||||||
|
*/
|
||||||
|
class Conditional {
|
||||||
|
public:
|
||||||
|
using Pointer = std::shared_ptr<Conditional>;
|
||||||
|
using List = std::list<Pointer>;
|
||||||
|
using Factory = hifi::SimpleFactory<Conditional, QString>;
|
||||||
|
|
||||||
|
virtual bool satisfied() = 0;
|
||||||
|
virtual bool parseParameters(const QJsonValue& parameters) { return true; }
|
||||||
|
|
||||||
|
static Pointer parse(const QJsonValue& json);
|
||||||
|
static void registerBuilder(const QString& name, Factory::Builder builder);
|
||||||
|
static Factory& getFactory() { return _factory; }
|
||||||
|
protected:
|
||||||
|
static Factory _factory;
|
||||||
|
};
|
||||||
|
|
||||||
|
class EndpointConditional : public Conditional {
|
||||||
|
public:
|
||||||
|
EndpointConditional(Endpoint::Pointer endpoint) : _endpoint(endpoint) { }
|
||||||
|
virtual bool satisfied() override;
|
||||||
|
private:
|
||||||
|
Endpoint::Pointer _endpoint;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#define REGISTER_CONDITIONAL_CLASS(classEntry) \
|
||||||
|
private: \
|
||||||
|
using Registrar = Conditional::Factory::Registrar<classEntry>; \
|
||||||
|
static Registrar _registrar;
|
||||||
|
|
||||||
|
#define REGISTER_CONDITIONAL_CLASS_INSTANCE(classEntry, className) \
|
||||||
|
classEntry::Registrar classEntry::_registrar(className, Conditional::getFactory());
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
|
@ -18,18 +18,27 @@
|
||||||
|
|
||||||
using namespace controller;
|
using namespace controller;
|
||||||
|
|
||||||
|
Filter::Factory Filter::_factory;
|
||||||
|
|
||||||
|
REGISTER_FILTER_CLASS_INSTANCE(InvertFilter, "invert")
|
||||||
|
REGISTER_FILTER_CLASS_INSTANCE(ConstrainToIntegerFilter, "constrainToInteger")
|
||||||
|
REGISTER_FILTER_CLASS_INSTANCE(ConstrainToPositiveIntegerFilter, "constrainToPositiveInteger")
|
||||||
|
REGISTER_FILTER_CLASS_INSTANCE(ScaleFilter, "scale")
|
||||||
|
REGISTER_FILTER_CLASS_INSTANCE(ClampFilter, "clamp")
|
||||||
|
REGISTER_FILTER_CLASS_INSTANCE(DeadZoneFilter, "deadZone")
|
||||||
|
REGISTER_FILTER_CLASS_INSTANCE(PulseFilter, "pulse")
|
||||||
|
|
||||||
|
|
||||||
const QString JSON_FILTER_TYPE = QStringLiteral("type");
|
const QString JSON_FILTER_TYPE = QStringLiteral("type");
|
||||||
const QString JSON_FILTER_PARAMS = QStringLiteral("params");
|
const QString JSON_FILTER_PARAMS = QStringLiteral("params");
|
||||||
|
|
||||||
Filter::Factory Filter::_factory;
|
|
||||||
|
|
||||||
Filter::Pointer Filter::parse(const QJsonObject& json) {
|
Filter::Pointer Filter::parse(const QJsonObject& json) {
|
||||||
// The filter is an object, now let s check for type and potential arguments
|
// The filter is an object, now let s check for type and potential arguments
|
||||||
Filter::Pointer filter;
|
Filter::Pointer filter;
|
||||||
auto filterType = json[JSON_FILTER_TYPE];
|
auto filterType = json[JSON_FILTER_TYPE];
|
||||||
if (filterType.isString()) {
|
if (filterType.isString()) {
|
||||||
filter.reset(Filter::getFactory().create(filterType.toString().toStdString()));
|
filter = Filter::getFactory().create(filterType.toString());
|
||||||
if (filter) {
|
if (filter) {
|
||||||
// Filter is defined, need to read the parameters and validate
|
// Filter is defined, need to read the parameters and validate
|
||||||
auto parameters = json[JSON_FILTER_PARAMS];
|
auto parameters = json[JSON_FILTER_PARAMS];
|
||||||
|
@ -44,7 +53,6 @@ Filter::Pointer Filter::parse(const QJsonObject& json) {
|
||||||
return Filter::Pointer();
|
return Filter::Pointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
ScaleFilter::FactoryEntryBuilder ScaleFilter::_factoryEntryBuilder;
|
|
||||||
|
|
||||||
bool ScaleFilter::parseParameters(const QJsonArray& parameters) {
|
bool ScaleFilter::parseParameters(const QJsonArray& parameters) {
|
||||||
if (parameters.size() > 1) {
|
if (parameters.size() > 1) {
|
||||||
|
@ -53,10 +61,6 @@ bool ScaleFilter::parseParameters(const QJsonArray& parameters) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
InvertFilter::FactoryEntryBuilder InvertFilter::_factoryEntryBuilder;
|
|
||||||
|
|
||||||
|
|
||||||
ClampFilter::FactoryEntryBuilder ClampFilter::_factoryEntryBuilder;
|
|
||||||
|
|
||||||
bool ClampFilter::parseParameters(const QJsonArray& parameters) {
|
bool ClampFilter::parseParameters(const QJsonArray& parameters) {
|
||||||
if (parameters.size() > 1) {
|
if (parameters.size() > 1) {
|
||||||
|
@ -68,7 +72,6 @@ bool ClampFilter::parseParameters(const QJsonArray& parameters) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
DeadZoneFilter::FactoryEntryBuilder DeadZoneFilter::_factoryEntryBuilder;
|
|
||||||
|
|
||||||
float DeadZoneFilter::apply(float value) const {
|
float DeadZoneFilter::apply(float value) const {
|
||||||
float scale = 1.0f / (1.0f - _min);
|
float scale = 1.0f / (1.0f - _min);
|
||||||
|
@ -85,8 +88,6 @@ bool DeadZoneFilter::parseParameters(const QJsonArray& parameters) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
PulseFilter::FactoryEntryBuilder PulseFilter::_factoryEntryBuilder;
|
|
||||||
|
|
||||||
|
|
||||||
float PulseFilter::apply(float value) const {
|
float PulseFilter::apply(float value) const {
|
||||||
float result = 0.0f;
|
float result = 0.0f;
|
||||||
|
@ -110,7 +111,4 @@ bool PulseFilter::parseParameters(const QJsonArray& parameters) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConstrainToIntegerFilter::FactoryEntryBuilder ConstrainToIntegerFilter::_factoryEntryBuilder;
|
|
||||||
|
|
||||||
ConstrainToPositiveIntegerFilter::FactoryEntryBuilder ConstrainToPositiveIntegerFilter::_factoryEntryBuilder;
|
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,10 @@
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <functional>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
|
#include <shared/Factory.h>
|
||||||
|
|
||||||
#include <GLMHelpers.h>
|
#include <GLMHelpers.h>
|
||||||
|
|
||||||
#include <QtCore/QEasingCurve>
|
#include <QtCore/QEasingCurve>
|
||||||
|
@ -23,81 +24,35 @@
|
||||||
class QJsonObject;
|
class QJsonObject;
|
||||||
class QJsonArray;
|
class QJsonArray;
|
||||||
|
|
||||||
|
|
||||||
namespace controller {
|
namespace controller {
|
||||||
|
|
||||||
// Encapsulates part of a filter chain
|
// Encapsulates part of a filter chain
|
||||||
class Filter {
|
class Filter {
|
||||||
public:
|
public:
|
||||||
virtual float apply(float value) const = 0;
|
|
||||||
|
|
||||||
using Pointer = std::shared_ptr<Filter>;
|
using Pointer = std::shared_ptr<Filter>;
|
||||||
using List = std::list<Pointer>;
|
using List = std::list<Pointer>;
|
||||||
using Lambda = std::function<float(float)>;
|
using Lambda = std::function<float(float)>;
|
||||||
|
using Factory = hifi::SimpleFactory<Filter, QString>;
|
||||||
|
|
||||||
|
virtual float apply(float value) const = 0;
|
||||||
// Factory features
|
// Factory features
|
||||||
virtual bool parseParameters(const QJsonArray& parameters) { return true; }
|
virtual bool parseParameters(const QJsonArray& parameters) { return true; }
|
||||||
|
|
||||||
class Factory {
|
static Pointer parse(const QJsonObject& json);
|
||||||
public:
|
static void registerBuilder(const QString& name, Factory::Builder builder);
|
||||||
|
|
||||||
class Entry {
|
|
||||||
public:
|
|
||||||
virtual Filter* create() const = 0;
|
|
||||||
virtual const char* getName() const = 0;
|
|
||||||
|
|
||||||
Entry() {};
|
|
||||||
virtual ~Entry() {};
|
|
||||||
};
|
|
||||||
|
|
||||||
template <class T> class ClassEntry : public Entry {
|
|
||||||
public:
|
|
||||||
virtual Filter* create() const { return (Filter*) new T(); }
|
|
||||||
virtual const char* getName() const { return T::getName(); };
|
|
||||||
|
|
||||||
ClassEntry() {};
|
|
||||||
virtual ~ClassEntry() = default;
|
|
||||||
|
|
||||||
class Builder {
|
|
||||||
public:
|
|
||||||
Builder() {
|
|
||||||
std::shared_ptr<Entry> classEntry(new ClassEntry<T>());
|
|
||||||
Filter::getFactory().registerEntry(classEntry);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
using EntryMap = std::map<std::string, std::shared_ptr<Entry>>;
|
|
||||||
|
|
||||||
void registerEntry(std::shared_ptr<Entry>& entry) {
|
|
||||||
if (entry) {
|
|
||||||
_entries.insert(EntryMap::value_type(entry->getName(), entry));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Filter* create(const std::string& name) const {
|
|
||||||
const auto& entryIt = _entries.find(name);
|
|
||||||
if (entryIt != _entries.end()) {
|
|
||||||
return (*entryIt).second->create();
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
EntryMap _entries;
|
|
||||||
};
|
|
||||||
|
|
||||||
static Filter::Pointer parse(const QJsonObject& json);
|
|
||||||
static Factory& getFactory() { return _factory; }
|
static Factory& getFactory() { return _factory; }
|
||||||
protected:
|
protected:
|
||||||
static Factory _factory;
|
static Factory _factory;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#define REGISTER_FILTER_CLASS(classEntry, className) \
|
#define REGISTER_FILTER_CLASS(classEntry) \
|
||||||
static const char* getName() { return className; } \
|
private: \
|
||||||
using FactoryEntryBuilder = Filter::Factory::ClassEntry<classEntry>::Builder;\
|
using Registrar = Filter::Factory::Registrar<classEntry>; \
|
||||||
static FactoryEntryBuilder _factoryEntryBuilder;
|
static Registrar _registrar;
|
||||||
|
|
||||||
|
#define REGISTER_FILTER_CLASS_INSTANCE(classEntry, className) \
|
||||||
|
classEntry::Registrar classEntry::_registrar(className, Filter::getFactory());
|
||||||
|
|
||||||
namespace controller {
|
namespace controller {
|
||||||
|
|
||||||
|
@ -123,8 +78,8 @@ namespace controller {
|
||||||
};
|
};
|
||||||
|
|
||||||
class ScaleFilter : public Filter {
|
class ScaleFilter : public Filter {
|
||||||
|
REGISTER_FILTER_CLASS(ScaleFilter);
|
||||||
public:
|
public:
|
||||||
REGISTER_FILTER_CLASS(ScaleFilter, "scale");
|
|
||||||
ScaleFilter() {}
|
ScaleFilter() {}
|
||||||
ScaleFilter(float scale): _scale(scale) {}
|
ScaleFilter(float scale): _scale(scale) {}
|
||||||
|
|
||||||
|
@ -138,8 +93,8 @@ namespace controller {
|
||||||
};
|
};
|
||||||
|
|
||||||
class InvertFilter : public ScaleFilter {
|
class InvertFilter : public ScaleFilter {
|
||||||
|
REGISTER_FILTER_CLASS(InvertFilter);
|
||||||
public:
|
public:
|
||||||
REGISTER_FILTER_CLASS(InvertFilter, "invert");
|
|
||||||
InvertFilter() : ScaleFilter(-1.0f) {}
|
InvertFilter() : ScaleFilter(-1.0f) {}
|
||||||
|
|
||||||
virtual bool parseParameters(const QJsonArray& parameters) { return true; }
|
virtual bool parseParameters(const QJsonArray& parameters) { return true; }
|
||||||
|
@ -148,8 +103,8 @@ namespace controller {
|
||||||
};
|
};
|
||||||
|
|
||||||
class ClampFilter : public Filter {
|
class ClampFilter : public Filter {
|
||||||
|
REGISTER_FILTER_CLASS(ClampFilter);
|
||||||
public:
|
public:
|
||||||
REGISTER_FILTER_CLASS(ClampFilter, "clamp");
|
|
||||||
ClampFilter(float min = 0.0, float max = 1.0) : _min(min), _max(max) {};
|
ClampFilter(float min = 0.0, float max = 1.0) : _min(min), _max(max) {};
|
||||||
|
|
||||||
virtual float apply(float value) const override {
|
virtual float apply(float value) const override {
|
||||||
|
@ -162,8 +117,8 @@ namespace controller {
|
||||||
};
|
};
|
||||||
|
|
||||||
class DeadZoneFilter : public Filter {
|
class DeadZoneFilter : public Filter {
|
||||||
|
REGISTER_FILTER_CLASS(DeadZoneFilter);
|
||||||
public:
|
public:
|
||||||
REGISTER_FILTER_CLASS(DeadZoneFilter, "deadZone");
|
|
||||||
DeadZoneFilter(float min = 0.0) : _min(min) {};
|
DeadZoneFilter(float min = 0.0) : _min(min) {};
|
||||||
|
|
||||||
virtual float apply(float value) const override;
|
virtual float apply(float value) const override;
|
||||||
|
@ -173,8 +128,8 @@ namespace controller {
|
||||||
};
|
};
|
||||||
|
|
||||||
class PulseFilter : public Filter {
|
class PulseFilter : public Filter {
|
||||||
|
REGISTER_FILTER_CLASS(PulseFilter);
|
||||||
public:
|
public:
|
||||||
REGISTER_FILTER_CLASS(PulseFilter, "pulse");
|
|
||||||
PulseFilter() {}
|
PulseFilter() {}
|
||||||
PulseFilter(float interval) : _interval(interval) {}
|
PulseFilter(float interval) : _interval(interval) {}
|
||||||
|
|
||||||
|
@ -189,8 +144,8 @@ namespace controller {
|
||||||
};
|
};
|
||||||
|
|
||||||
class ConstrainToIntegerFilter : public Filter {
|
class ConstrainToIntegerFilter : public Filter {
|
||||||
|
REGISTER_FILTER_CLASS(ConstrainToIntegerFilter);
|
||||||
public:
|
public:
|
||||||
REGISTER_FILTER_CLASS(ConstrainToIntegerFilter, "constrainToInteger");
|
|
||||||
ConstrainToIntegerFilter() {};
|
ConstrainToIntegerFilter() {};
|
||||||
|
|
||||||
virtual float apply(float value) const override {
|
virtual float apply(float value) const override {
|
||||||
|
@ -200,8 +155,8 @@ namespace controller {
|
||||||
};
|
};
|
||||||
|
|
||||||
class ConstrainToPositiveIntegerFilter : public Filter {
|
class ConstrainToPositiveIntegerFilter : public Filter {
|
||||||
|
REGISTER_FILTER_CLASS(ConstrainToPositiveIntegerFilter);
|
||||||
public:
|
public:
|
||||||
REGISTER_FILTER_CLASS(ConstrainToPositiveIntegerFilter, "constrainToPositiveInteger");
|
|
||||||
ConstrainToPositiveIntegerFilter() {};
|
ConstrainToPositiveIntegerFilter() {};
|
||||||
|
|
||||||
virtual float apply(float value) const override {
|
virtual float apply(float value) const override {
|
||||||
|
|
|
@ -23,14 +23,12 @@ namespace controller {
|
||||||
|
|
||||||
class Mapping {
|
class Mapping {
|
||||||
public:
|
public:
|
||||||
// Map of source channels to route lists
|
|
||||||
using Map = std::map<Endpoint::Pointer, Route::List>;
|
|
||||||
using Pointer = std::shared_ptr<Mapping>;
|
using Pointer = std::shared_ptr<Mapping>;
|
||||||
|
using List = Route::List;
|
||||||
|
|
||||||
Mapping(const QString& name) : name(name) {}
|
Mapping(const QString& name) : name(name) {}
|
||||||
|
|
||||||
Map channelMappings;
|
List routes;
|
||||||
|
|
||||||
QString name;
|
QString name;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace controller {
|
||||||
|
|
||||||
Pose::Pose(const vec3& translation, const quat& rotation,
|
Pose::Pose(const vec3& translation, const quat& rotation,
|
||||||
const vec3& velocity, const quat& angularVelocity) :
|
const vec3& velocity, const quat& angularVelocity) :
|
||||||
translation(translation), rotation(rotation), velocity(velocity), angularVelocity(angularVelocity) { }
|
translation(translation), rotation(rotation), velocity(velocity), angularVelocity(angularVelocity), valid (true) { }
|
||||||
|
|
||||||
bool Pose::operator==(const Pose& right) const {
|
bool Pose::operator==(const Pose& right) const {
|
||||||
// invalid poses return false for comparison, even against identical invalid poses, like NaN
|
// invalid poses return false for comparison, even against identical invalid poses, like NaN
|
||||||
|
|
|
@ -12,8 +12,7 @@
|
||||||
|
|
||||||
#include "Endpoint.h"
|
#include "Endpoint.h"
|
||||||
#include "Filter.h"
|
#include "Filter.h"
|
||||||
|
#include "Conditional.h"
|
||||||
class QJsonObject;
|
|
||||||
|
|
||||||
namespace controller {
|
namespace controller {
|
||||||
/*
|
/*
|
||||||
|
@ -23,6 +22,7 @@ namespace controller {
|
||||||
public:
|
public:
|
||||||
Endpoint::Pointer source;
|
Endpoint::Pointer source;
|
||||||
Endpoint::Pointer destination;
|
Endpoint::Pointer destination;
|
||||||
|
Conditional::Pointer conditional;
|
||||||
Filter::List filters;
|
Filter::List filters;
|
||||||
|
|
||||||
using Pointer = std::shared_ptr<Route>;
|
using Pointer = std::shared_ptr<Route>;
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
|
|
||||||
#include "UserInputMapper.h"
|
#include "UserInputMapper.h"
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
#include <QtCore/QThread>
|
#include <QtCore/QThread>
|
||||||
#include <QtCore/QFile>
|
#include <QtCore/QFile>
|
||||||
|
|
||||||
|
@ -203,6 +205,9 @@ public:
|
||||||
virtual Pose pose() override { return _currentPose; }
|
virtual Pose pose() override { return _currentPose; }
|
||||||
virtual void apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) override {
|
virtual void apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) override {
|
||||||
_currentPose = newValue;
|
_currentPose = newValue;
|
||||||
|
if (!_currentPose.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!(_input == Input::INVALID_INPUT)) {
|
if (!(_input == Input::INVALID_INPUT)) {
|
||||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||||
userInputMapper->setActionState(Action(_input.getChannel()), _currentPose);
|
userInputMapper->setActionState(Action(_input.getChannel()), _currentPose);
|
||||||
|
@ -253,9 +258,7 @@ void UserInputMapper::registerDevice(InputDevice* device) {
|
||||||
} else if (input.device == ACTIONS_DEVICE) {
|
} else if (input.device == ACTIONS_DEVICE) {
|
||||||
endpoint = std::make_shared<ActionEndpoint>(input);
|
endpoint = std::make_shared<ActionEndpoint>(input);
|
||||||
} else {
|
} else {
|
||||||
endpoint = std::make_shared<LambdaEndpoint>([=] {
|
endpoint = std::make_shared<InputEndpoint>(input);
|
||||||
return proxy->getValue(input, 0);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
_inputsByEndpoint[endpoint] = input;
|
_inputsByEndpoint[endpoint] = input;
|
||||||
_endpointsByInput[input] = endpoint;
|
_endpointsByInput[input] = endpoint;
|
||||||
|
@ -265,17 +268,46 @@ void UserInputMapper::registerDevice(InputDevice* device) {
|
||||||
auto mapping = loadMapping(device->getDefaultMappingConfig());
|
auto mapping = loadMapping(device->getDefaultMappingConfig());
|
||||||
if (mapping) {
|
if (mapping) {
|
||||||
_mappingsByDevice[deviceID] = mapping;
|
_mappingsByDevice[deviceID] = mapping;
|
||||||
for (const auto& entry : mapping->channelMappings) {
|
auto& defaultRoutes = _defaultMapping->routes;
|
||||||
const auto& source = entry.first;
|
|
||||||
const auto& routes = entry.second;
|
// New routes for a device get injected IN FRONT of existing routes. Routes
|
||||||
auto& list = _defaultMapping->channelMappings[source];
|
// are processed in order so this ensures that the standard -> action processing
|
||||||
list.insert(list.end(), routes.begin(), routes.end());
|
// takes place after all of the hardware -> standard or hardware -> action processing
|
||||||
}
|
// because standard -> action is the first set of routes added.
|
||||||
|
defaultRoutes.insert(defaultRoutes.begin(), mapping->routes.begin(), mapping->routes.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
emit hardwareChanged();
|
emit hardwareChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME remove the associated device mappings
|
||||||
|
void UserInputMapper::removeDevice(int deviceID) {
|
||||||
|
auto proxyEntry = _registeredDevices.find(deviceID);
|
||||||
|
if (_registeredDevices.end() == proxyEntry) {
|
||||||
|
qCWarning(controllers) << "Attempted to remove unknown device " << deviceID;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto proxy = proxyEntry->second;
|
||||||
|
auto mappingsEntry = _mappingsByDevice.find(deviceID);
|
||||||
|
if (_mappingsByDevice.end() != mappingsEntry) {
|
||||||
|
const auto& mapping = mappingsEntry->second;
|
||||||
|
const auto& deviceRoutes = mapping->routes;
|
||||||
|
std::set<Route::Pointer> routeSet(deviceRoutes.begin(), deviceRoutes.end());
|
||||||
|
|
||||||
|
auto& defaultRoutes = _defaultMapping->routes;
|
||||||
|
std::remove_if(defaultRoutes.begin(), defaultRoutes.end(), [&](Route::Pointer route)->bool {
|
||||||
|
return routeSet.count(route) != 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
_mappingsByDevice.erase(mappingsEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
_registeredDevices.erase(proxyEntry);
|
||||||
|
|
||||||
|
emit hardwareChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
DeviceProxy::Pointer UserInputMapper::getDeviceProxy(const Input& input) {
|
DeviceProxy::Pointer UserInputMapper::getDeviceProxy(const Input& input) {
|
||||||
auto device = _registeredDevices.find(input.getDevice());
|
auto device = _registeredDevices.find(input.getDevice());
|
||||||
if (device != _registeredDevices.end()) {
|
if (device != _registeredDevices.end()) {
|
||||||
|
@ -347,11 +379,6 @@ Input UserInputMapper::findDeviceInput(const QString& inputName) const {
|
||||||
return Input::INVALID_INPUT;
|
return Input::INVALID_INPUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME remove the associated device mappings
|
|
||||||
void UserInputMapper::removeDevice(int device) {
|
|
||||||
_registeredDevices.erase(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
void fixBisectedAxis(float& full, float& negative, float& positive) {
|
void fixBisectedAxis(float& full, float& negative, float& positive) {
|
||||||
full = full + (negative * -1.0f) + positive;
|
full = full + (negative * -1.0f) + positive;
|
||||||
negative = full >= 0.0f ? 0.0f : full * -1.0f;
|
negative = full >= 0.0f ? 0.0f : full * -1.0f;
|
||||||
|
@ -534,12 +561,6 @@ Input UserInputMapper::makeStandardInput(controller::StandardPoseChannel pose) {
|
||||||
return Input(STANDARD_DEVICE, pose, ChannelType::POSE);
|
return Input(STANDARD_DEVICE, pose, ChannelType::POSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Pass {
|
|
||||||
HARDWARE_PASS,
|
|
||||||
STANDARD_PASS,
|
|
||||||
NUM_PASSES
|
|
||||||
};
|
|
||||||
|
|
||||||
void UserInputMapper::update() {
|
void UserInputMapper::update() {
|
||||||
static auto deviceNames = getDeviceNames();
|
static auto deviceNames = getDeviceNames();
|
||||||
_overrideValues.clear();
|
_overrideValues.clear();
|
||||||
|
@ -549,16 +570,8 @@ void UserInputMapper::update() {
|
||||||
|
|
||||||
// Now process the current values for each level of the stack
|
// Now process the current values for each level of the stack
|
||||||
for (auto& mapping : _activeMappings) {
|
for (auto& mapping : _activeMappings) {
|
||||||
for (int pass = HARDWARE_PASS; pass < NUM_PASSES; ++pass) {
|
for (const auto& route : mapping->routes) {
|
||||||
for (const auto& mappingEntry : mapping->channelMappings) {
|
const auto& source = route->source;
|
||||||
const auto& source = mappingEntry.first;
|
|
||||||
if (_inputsByEndpoint.count(source)) {
|
|
||||||
auto sourceInput = _inputsByEndpoint[source];
|
|
||||||
if ((sourceInput.device == STANDARD_DEVICE) ^ (pass == STANDARD_PASS)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Endpoints can only be read once (though a given mapping can route them to
|
// Endpoints can only be read once (though a given mapping can route them to
|
||||||
// multiple places). Consider... If the default is to wire the A button to JUMP
|
// multiple places). Consider... If the default is to wire the A button to JUMP
|
||||||
// and someone else wires it to CONTEXT_MENU, I don't want both to occur when
|
// and someone else wires it to CONTEXT_MENU, I don't want both to occur when
|
||||||
|
@ -568,10 +581,6 @@ void UserInputMapper::update() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the value to all the routes
|
|
||||||
const auto& routes = mappingEntry.second;
|
|
||||||
|
|
||||||
for (const auto& route : routes) {
|
|
||||||
const auto& destination = route->destination;
|
const auto& destination = route->destination;
|
||||||
// THis could happen if the route destination failed to create
|
// THis could happen if the route destination failed to create
|
||||||
// FIXME: Maybe do not create the route if the destination failed and avoid this case ?
|
// FIXME: Maybe do not create the route if the destination failed and avoid this case ?
|
||||||
|
@ -583,6 +592,12 @@ void UserInputMapper::update() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (route->conditional) {
|
||||||
|
if (!route->conditional->satisfied()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Standard controller destinations can only be can only be used once.
|
// Standard controller destinations can only be can only be used once.
|
||||||
if (getStandardDeviceID() == destination->getInput().getDevice()) {
|
if (getStandardDeviceID() == destination->getInput().getDevice()) {
|
||||||
writtenEndpoints.insert(destination);
|
writtenEndpoints.insert(destination);
|
||||||
|
@ -601,6 +616,7 @@ void UserInputMapper::update() {
|
||||||
// no filters yet for pose
|
// no filters yet for pose
|
||||||
destination->apply(value, Pose(), source);
|
destination->apply(value, Pose(), source);
|
||||||
} else {
|
} else {
|
||||||
|
// Fetch the value, may have been overriden by previous loopback routes
|
||||||
float value = getValue(source);
|
float value = getValue(source);
|
||||||
|
|
||||||
// Apply each of the filters.
|
// Apply each of the filters.
|
||||||
|
@ -617,8 +633,6 @@ void UserInputMapper::update() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Endpoint::Pointer UserInputMapper::endpointFor(const QJSValue& endpoint) {
|
Endpoint::Pointer UserInputMapper::endpointFor(const QJSValue& endpoint) {
|
||||||
if (endpoint.isNumber()) {
|
if (endpoint.isNumber()) {
|
||||||
|
@ -777,6 +791,7 @@ Mapping::Pointer UserInputMapper::loadMapping(const QString& jsonFile) {
|
||||||
const QString JSON_NAME = QStringLiteral("name");
|
const QString JSON_NAME = QStringLiteral("name");
|
||||||
const QString JSON_CHANNELS = QStringLiteral("channels");
|
const QString JSON_CHANNELS = QStringLiteral("channels");
|
||||||
const QString JSON_CHANNEL_FROM = QStringLiteral("from");
|
const QString JSON_CHANNEL_FROM = QStringLiteral("from");
|
||||||
|
const QString JSON_CHANNEL_WHEN = QStringLiteral("when");
|
||||||
const QString JSON_CHANNEL_TO = QStringLiteral("to");
|
const QString JSON_CHANNEL_TO = QStringLiteral("to");
|
||||||
const QString JSON_CHANNEL_FILTERS = QStringLiteral("filters");
|
const QString JSON_CHANNEL_FILTERS = QStringLiteral("filters");
|
||||||
|
|
||||||
|
@ -791,6 +806,20 @@ Endpoint::Pointer UserInputMapper::parseEndpoint(const QJsonValue& value) {
|
||||||
return Endpoint::Pointer();
|
return Endpoint::Pointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Conditional::Pointer UserInputMapper::parseConditional(const QJsonValue& value) {
|
||||||
|
if (value.isString()) {
|
||||||
|
auto input = findDeviceInput(value.toString());
|
||||||
|
auto endpoint = endpointFor(input);
|
||||||
|
if (!endpoint) {
|
||||||
|
return Conditional::Pointer();
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_shared<EndpointConditional>(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Conditional::parse(value);
|
||||||
|
}
|
||||||
|
|
||||||
Route::Pointer UserInputMapper::parseRoute(const QJsonValue& value) {
|
Route::Pointer UserInputMapper::parseRoute(const QJsonValue& value) {
|
||||||
if (!value.isObject()) {
|
if (!value.isObject()) {
|
||||||
return Route::Pointer();
|
return Route::Pointer();
|
||||||
|
@ -809,11 +838,25 @@ Route::Pointer UserInputMapper::parseRoute(const QJsonValue& value) {
|
||||||
return Route::Pointer();
|
return Route::Pointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (obj.contains(JSON_CHANNEL_WHEN)) {
|
||||||
|
auto when = parseConditional(obj[JSON_CHANNEL_WHEN]);
|
||||||
|
if (!when) {
|
||||||
|
qWarning() << "Invalid route conditional " << obj[JSON_CHANNEL_TO];
|
||||||
|
return Route::Pointer();
|
||||||
|
}
|
||||||
|
result->conditional = when;
|
||||||
|
}
|
||||||
|
|
||||||
const auto& filtersValue = obj[JSON_CHANNEL_FILTERS];
|
const auto& filtersValue = obj[JSON_CHANNEL_FILTERS];
|
||||||
|
// FIXME support strings for filters with no parameters, both in the array and at the top level...
|
||||||
|
// i.e.
|
||||||
|
// { "from": "Standard.DU", "to" : "Actions.LONGITUDINAL_FORWARD", "filters" : "invert" },
|
||||||
|
// and
|
||||||
|
// { "from": "Standard.DU", "to" : "Actions.LONGITUDINAL_FORWARD", "filters" : [ "invert", "constrainToInteger" ] },
|
||||||
if (filtersValue.isArray()) {
|
if (filtersValue.isArray()) {
|
||||||
auto filtersArray = filtersValue.toArray();
|
auto filtersArray = filtersValue.toArray();
|
||||||
for (auto filterValue : filtersArray) {
|
for (auto filterValue : filtersArray) {
|
||||||
if (filterValue.isObject()) {
|
if (!filterValue.isObject()) {
|
||||||
qWarning() << "Invalid filter " << filterValue;
|
qWarning() << "Invalid filter " << filterValue;
|
||||||
return Route::Pointer();
|
return Route::Pointer();
|
||||||
}
|
}
|
||||||
|
@ -836,16 +879,14 @@ Mapping::Pointer UserInputMapper::parseMapping(const QJsonValue& json) {
|
||||||
auto obj = json.toObject();
|
auto obj = json.toObject();
|
||||||
auto mapping = std::make_shared<Mapping>("default");
|
auto mapping = std::make_shared<Mapping>("default");
|
||||||
mapping->name = obj[JSON_NAME].toString();
|
mapping->name = obj[JSON_NAME].toString();
|
||||||
mapping->channelMappings.clear();
|
|
||||||
const auto& jsonChannels = obj[JSON_CHANNELS].toArray();
|
const auto& jsonChannels = obj[JSON_CHANNELS].toArray();
|
||||||
Mapping::Map map;
|
|
||||||
for (const auto& channelIt : jsonChannels) {
|
for (const auto& channelIt : jsonChannels) {
|
||||||
Route::Pointer route = parseRoute(channelIt);
|
Route::Pointer route = parseRoute(channelIt);
|
||||||
if (!route) {
|
if (!route) {
|
||||||
qWarning() << "Couldn't parse route";
|
qWarning() << "Couldn't parse route";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
mapping->channelMappings[route->source].push_back(route);
|
mapping->routes.push_back(route);
|
||||||
}
|
}
|
||||||
return mapping;
|
return mapping;
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,6 +150,7 @@ namespace controller {
|
||||||
Endpoint::Pointer compositeEndpointFor(Endpoint::Pointer first, Endpoint::Pointer second);
|
Endpoint::Pointer compositeEndpointFor(Endpoint::Pointer first, Endpoint::Pointer second);
|
||||||
Mapping::Pointer parseMapping(const QJsonValue& json);
|
Mapping::Pointer parseMapping(const QJsonValue& json);
|
||||||
Route::Pointer parseRoute(const QJsonValue& value);
|
Route::Pointer parseRoute(const QJsonValue& value);
|
||||||
|
Conditional::Pointer parseConditional(const QJsonValue& value);
|
||||||
Endpoint::Pointer parseEndpoint(const QJsonValue& value);
|
Endpoint::Pointer parseEndpoint(const QJsonValue& value);
|
||||||
|
|
||||||
InputToEndpointMap _endpointsByInput;
|
InputToEndpointMap _endpointsByInput;
|
||||||
|
|
|
@ -41,7 +41,7 @@ void RouteBuilderProxy::to(const QScriptValue& destination) {
|
||||||
void RouteBuilderProxy::to(const Endpoint::Pointer& destination) {
|
void RouteBuilderProxy::to(const Endpoint::Pointer& destination) {
|
||||||
auto sourceEndpoint = _route->source;
|
auto sourceEndpoint = _route->source;
|
||||||
_route->destination = destination;
|
_route->destination = destination;
|
||||||
_mapping->channelMappings[sourceEndpoint].push_back(_route);
|
_mapping->routes.push_back(_route);
|
||||||
deleteLater();
|
deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
51
libraries/shared/src/shared/Factory.h
Normal file
51
libraries/shared/src/shared/Factory.h
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis 2015/10/09
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#ifndef hifi_Shared_Factory_h
|
||||||
|
#define hifi_Shared_Factory_h
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace hifi {
|
||||||
|
|
||||||
|
template <typename T, typename Key>
|
||||||
|
class SimpleFactory {
|
||||||
|
public:
|
||||||
|
using Pointer = std::shared_ptr<T>;
|
||||||
|
using Builder = std::function<Pointer()>;
|
||||||
|
using BuilderMap = std::map<Key, Builder>;
|
||||||
|
|
||||||
|
void registerBuilder(const Key& name, Builder builder) {
|
||||||
|
// FIXME don't allow name collisions
|
||||||
|
_builders[name] = builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pointer create(const Key& name) const {
|
||||||
|
const auto& entryIt = _builders.find(name);
|
||||||
|
if (entryIt != _builders.end()) {
|
||||||
|
return (*entryIt).second();
|
||||||
|
}
|
||||||
|
return Pointer();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Impl>
|
||||||
|
class Registrar {
|
||||||
|
public:
|
||||||
|
Registrar(const Key& name, SimpleFactory& factory) {
|
||||||
|
factory.registerBuilder(name, [] { return std::make_shared<Impl>(); });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
protected:
|
||||||
|
BuilderMap _builders;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in a new issue