Skip to content

Commit

Permalink
Add an altitude concept to describe filter priorities
Browse files Browse the repository at this point in the history
Altitude is now declared on an AutoFilter as a static member of that class, as so:

```C++
class MyFilter {
    static const autowiring::altitude altitude = autowiring::altitude::Instrumentation;
};
```

The remainder of the AutoFilter declaration is unchanged.  By default, AutoFilters marked `Deferred` will run at a higher altitude than other filters.

Currently, altitudes can only be easily declared on class-level AutoFilters.
  • Loading branch information
codemercenary committed Apr 20, 2015
1 parent bc52707 commit f9de267
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 f9de267

Please sign in to comment.