Skip to content

Commit

Permalink
Merge pull request #496 from leapmotion/feature-altitude
Browse files Browse the repository at this point in the history
Add an altitude concept to describe filter priorities
  • Loading branch information
Jonathan Marsden committed Apr 20, 2015
2 parents bc52707 + f9de267 commit 55837ed
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 10 deletions.
30 changes: 22 additions & 8 deletions autowiring/AutoFilterDescriptor.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved.
#pragma once
#include "AnySharedPointer.h"
#include "altitude.h"
#include "auto_arg.h"
#include "AutoFilterDescriptorInput.h"
#include "CallExtractor.h"
Expand All @@ -18,6 +19,7 @@ class Deferred;
struct AutoFilterDescriptorStub {
AutoFilterDescriptorStub(void) :
m_pType(nullptr),
m_altitude(autowiring::altitude::Standard),
m_pArgs(nullptr),
m_deferred(false),
m_arity(0),
Expand All @@ -40,8 +42,9 @@ struct AutoFilterDescriptorStub {
/// is required to carry information about the type of the proper member function to be called; t_extractedCall is
/// required to be instantiated by the caller and point to the AutoFilter proxy routine.
/// </summary>
AutoFilterDescriptorStub(const std::type_info* pType, const AutoFilterDescriptorInput* pArgs, bool deferred, t_extractedCall pCall) :
AutoFilterDescriptorStub(const std::type_info* pType, autowiring::altitude altitude, const AutoFilterDescriptorInput* pArgs, bool deferred, t_extractedCall pCall) :
m_pType(pType),
m_altitude(altitude),
m_pArgs(pArgs),
m_deferred(deferred),
m_arity(0),
Expand All @@ -61,6 +64,9 @@ struct AutoFilterDescriptorStub {
// Type of the subscriber itself
const std::type_info* m_pType;

// Altitude--controls when the filter gets called
autowiring::altitude m_altitude;

// This subscriber's argument types
// NOTE: This is a reference to a static generated list,
// therefore it MUST be const and MUST be shallow-copied.
Expand All @@ -87,6 +93,7 @@ struct AutoFilterDescriptorStub {

public:
// Accessor methods:
autowiring::altitude GetAltitude(void) const { return m_altitude; }
const std::type_info* GetType() const { return m_pType; }
size_t GetArity(void) const { return m_arity; }
size_t GetRequiredCount(void) const { return m_requiredCount; }
Expand Down Expand Up @@ -162,6 +169,10 @@ struct AutoFilterDescriptor:
std::static_pointer_cast<typename Decompose<decltype(&T::AutoFilter)>::type>(subscriber)
),
&typeid(T),
autowiring::altitude_of<
T,
CallExtractor<decltype(&T::AutoFilter)>::deferred ? autowiring::altitude::Dispatch : autowiring::altitude::Standard
>::value,
Decompose<decltype(&T::AutoFilter)>::template Enumerate<AutoFilterDescriptorInput, AutoFilterDescriptorInputT>::types,
CallExtractor<decltype(&T::AutoFilter)>::deferred,
&CallExtractor<decltype(&T::AutoFilter)>::template Call<&T::AutoFilter>
Expand All @@ -179,6 +190,7 @@ struct AutoFilterDescriptor:
AutoFilterDescriptor(
AnySharedPointer(std::make_shared<Fn>(std::forward<Fn>(fn))),
&typeid(Fn),
autowiring::altitude::Standard,
CallExtractor<decltype(&Fn::operator())>::template Enumerate<AutoFilterDescriptorInput, AutoFilterDescriptorInputT>::types,
false,
&CallExtractor<decltype(&Fn::operator())>::template Call<&Fn::operator()>
Expand All @@ -203,13 +215,13 @@ struct AutoFilterDescriptor:
///
/// The caller is responsible for decomposing the desired routine into the target AutoFilter call
/// </summary>
AutoFilterDescriptor(const AnySharedPointer& autoFilter, const std::type_info* pType, const AutoFilterDescriptorInput* pArgs, bool deferred, t_extractedCall pCall) :
AutoFilterDescriptorStub(pType, pArgs, deferred, pCall),
AutoFilterDescriptor(const AnySharedPointer& autoFilter, const std::type_info* pType, autowiring::altitude altitude, const AutoFilterDescriptorInput* pArgs, bool deferred, t_extractedCall pCall) :
AutoFilterDescriptorStub(pType, altitude, pArgs, deferred, pCall),
m_autoFilter(autoFilter)
{}

template<class RetType, class... Args>
AutoFilterDescriptor(RetType(*pfn)(Args...)):
AutoFilterDescriptor(RetType(*pfn)(Args...), autowiring::altitude altitude = autowiring::altitude::Standard) :
AutoFilterDescriptor(
// Token shared pointer, used to provide a pointer to pfn because we can't
// capture it in a template processing context. Hopefully this can be changed
Expand All @@ -223,7 +235,7 @@ struct AutoFilterDescriptor:

// The remainder is fairly straightforward
&typeid(pfn),

altitude,
CallExtractor<decltype(pfn)>::template Enumerate<AutoFilterDescriptorInput, AutoFilterDescriptorInputT>::types,
false,
CallExtractor<decltype(pfn)>::Call
Expand Down Expand Up @@ -262,9 +274,11 @@ struct AutoFilterDescriptor:
/// Default for std library sorting of unique elements
/// </summary>
bool operator<(const AutoFilterDescriptor& rhs) const {
if (m_pCall < rhs.m_pCall)
return true;
return m_autoFilter < rhs.m_autoFilter;
// This filter is "logically prior" to the right-hand side if this filter has a HIGHER altitude
// than the one on the right-hand side
return
std::tie(m_altitude, m_pCall, m_autoFilter) <
std::tie(rhs.m_altitude, rhs.m_pCall, rhs.m_autoFilter);
}

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions autowiring/AutoPacketFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#include "TypeRegistry.h"
#include CHRONO_HEADER
#include TYPE_TRAITS_HEADER
#include STL_UNORDERED_SET
#include <set>

class AutoPacketFactory;
class DispatchQueue;
Expand Down Expand Up @@ -41,7 +41,7 @@ class AutoPacketFactory:
std::shared_ptr<AutoPacketInternal> m_nextPacket;

// Collection of known subscribers
typedef std::unordered_set<AutoFilterDescriptor, std::hash<AutoFilterDescriptor>> t_autoFilterSet;
typedef std::set<AutoFilterDescriptor> t_autoFilterSet;
t_autoFilterSet m_autoFilters;

// Accumulators used to compute statistics about AutoPacket lifespan.
Expand Down
87 changes: 87 additions & 0 deletions autowiring/altitude.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved.
#pragma once

namespace autowiring {

/// <summary>
/// Defines the altitude enumeration concept for AutoFilter instances
/// </summary>
/// <remarks>
/// A filter altitude is an indicator to the AutoFilter scheduler about when a particular filter
/// should be scheduled to receive control. Altitude is a hard requirement, but is subject to
/// a number of stipulations as to when it applies:
///
/// 1) If two autofilters are both candidates to be run at the same time, the filter with the
/// higher altitude will be run first.
/// 2) If both filters have the same altitude, an arbitrary filter will be selected.
/// 3) When the current filter returns control (IE, its AutoFilter routine returns), the next
/// filter will be run.
/// 4) Deferred AutoFilters are a special case. A deferred AutoFilter is considered to have
/// returned control as soon as its execution has been scheduled; generally this happens very
/// fast.
/// 5) Altitudes only provide an order-of-execution guarantee if NO deferred AutoFilters have been
/// declared in the network.
/// </remarks>
enum class altitude {
// Highest altitude level. Reserved for temporary debug logic and other nonpermanent code that
// must run before all other filter levels
Highest = 0x9000,

// Instrumentation level, for use with instrumentation code. Instrumentation code often needs to
// observe the inputs to its AutoFilter before any other code has an opportunity to observe it,
// because this code needs information about
Instrumentation = 0x8000,

// Default altitude for Deferred autofilters. Deferred autofilters are guaranteed to return very
// quickly, even though they may do a lot of work, because they do not tie up the main thread.
Dispatch = 0x7000,

// Asynchronous filters are designed to be run with a higher priority than standard filters,
// but are still expected to complete very quickly. Because their speedy behavior is implemented
// by the filter, and not guaranteed by Autowiring, asynchronous filters are considered to
// have a lower priority than Deferred filters.
//
// It is expected that AutoFilters which are marked as Asynchronous will do the majority of their
// work in an std::async or other similar call.
Asynchronous = 0x6000,

// The realtime altitude is a higher-than-normal altitude which may have some tight timing requirements
// but does not run in a separate thread. Realtime filters run after deferred filters have been
// scheduled to run.
Realtime = 0x5000,

// Default altitude range. Unless otherwise specified, or the filter is marked Deferred, filters
// will normally execute at this priority level.
Standard = 0x4000,

// Altitude indicator for filters with no hard timing requirements. This is a convenient place to put
// filters that may have extensive CPU usage requirements, or which are not strongly impacted by timing.
// Analytics and diagnostics are typically suitable for execution at the passive level.
Passive = 0x3000,

// Lowest altitude level. Reserved for temporary debug logic and other nonpermanent code that
// must run after all other filter levels.
Lowest = 0x2000
};

inline altitude operator+(altitude alt, int v) {
return (altitude) ((int) alt + v);
}

/// <summary>
/// Extracts the altitude of type T, if declared, or infers it if not
/// </summary>
/// <param name="T">The outer type of the AutoFilter</param>
/// <param name="Default">The default to be used if one is not provied by T</param>
template<class T, altitude Default = altitude::Standard>
struct altitude_of {
template<class U>
static std::integral_constant<altitude, U::altitude> select(U*);

template<class U>
static std::integral_constant<altitude, Default> select(...);

static const altitude value = decltype(select<T>(nullptr))::value;
};

}
1 change: 1 addition & 0 deletions src/autowiring/AutoPacket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ bool AutoPacket::Wait(std::condition_variable& cv, const AutoFilterDescriptorInp
stub,
AutoFilterDescriptorStub(
&typeid(AutoPacketFactory),
autowiring::altitude::Dispatch,
inputs,
false,
[] (const AnySharedPointer& obj, AutoPacket&) {
Expand Down
1 change: 1 addition & 0 deletions src/autowiring/AutoPacketGraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <string>
#include <sstream>
#include FUNCTIONAL_HEADER
#include STL_UNORDERED_SET

AutoPacketGraph::AutoPacketGraph() {
}
Expand Down
1 change: 1 addition & 0 deletions src/autowiring/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ set(Autowiring_SRCS
AnySharedPointer.h
atomic_object.h
at_exit.h
altitude.h
auto_id.h
auto_future.h
auto_signal.h
Expand Down
53 changes: 53 additions & 0 deletions src/autowiring/test/AutoFilterAltitudeTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved.
#include "stdafx.h"
#include <autowiring/autowiring.h>

class AutoFilterAltitudeTest:
public testing::Test
{};

struct AltitudeValue {};

struct AltitudeMonotonicCounter {
std::atomic<int> order{0};
};

template<autowiring::altitude A>
struct HasProfilingAltitude {
static const autowiring::altitude altitude = A;

AutoRequired<AltitudeMonotonicCounter> ctr;
int order = -1;

void AutoFilter(const AltitudeValue& val) {
order = ++ctr->order;
}
};

TEST_F(AutoFilterAltitudeTest, AltitudeDetection) {
AutoFilterDescriptor desc(std::make_shared<HasProfilingAltitude<autowiring::altitude::Highest>>());
ASSERT_EQ(autowiring::altitude::Highest, desc.GetAltitude()) << "Filter altitude was not correctly inferred";
}

TEST_F(AutoFilterAltitudeTest, StandardAltitudeArrangement) {
AutoCurrentContext()->Initiate();

AutoRequired<HasProfilingAltitude<autowiring::altitude::Standard>> alt3;
AutoRequired<HasProfilingAltitude<autowiring::altitude::Asynchronous>> alt1;
AutoRequired<HasProfilingAltitude<autowiring::altitude::Realtime>> alt2;
AutoRequired<HasProfilingAltitude<autowiring::altitude::Passive>> alt4;
AutoRequired<HasProfilingAltitude<autowiring::altitude::Lowest>> alt5;
AutoRequired<HasProfilingAltitude<autowiring::altitude::Dispatch>> alt0;

AutoRequired<AutoPacketFactory> factory;
auto packet = factory->NewPacket();
packet->Decorate(AltitudeValue{});

// Now we verify things got invoked in the right order:
ASSERT_EQ(1, alt0->order);
ASSERT_EQ(2, alt1->order);
ASSERT_EQ(3, alt2->order);
ASSERT_EQ(4, alt3->order);
ASSERT_EQ(5, alt4->order);
ASSERT_EQ(6, alt5->order);
}
1 change: 1 addition & 0 deletions src/autowiring/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ set(AutowiringTest_SRCS
AutoConfigListingTest.cpp
AutoConfigParserTest.cpp
AutoConstructTest.cpp
AutoFilterAltitudeTest.cpp
AutoFilterCollapseRulesTest.cpp
AutoFilterDiagnosticsTest.cpp
AutoFilterFunctionTest.cpp
Expand Down
1 change: 1 addition & 0 deletions src/autowiring/test/ContextEnumeratorTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <autowiring/ContextEnumerator.h>
#include <algorithm>
#include MEMORY_HEADER
#include STL_UNORDERED_SET

class ContextEnumeratorTest:
public testing::Test
Expand Down

0 comments on commit 55837ed

Please sign in to comment.