13457 lines
468 KiB
C++
13457 lines
468 KiB
C++
/***
|
|
* ==++==
|
|
*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
*
|
|
* ==--==
|
|
* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
|
*
|
|
* agents.h
|
|
*
|
|
* Main public header file for ConcRT's asynchronous agents layer. This is the only header file a
|
|
* C++ program must include to use asynchronous agents.
|
|
*
|
|
* The core runtime, Parallel Patterns Library (PPL), and resource manager are defined in separate header files.
|
|
* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
|
****/
|
|
|
|
#pragma once
|
|
|
|
#include <crtdefs.h>
|
|
#include <concrt.h>
|
|
#include <stdexcept>
|
|
#include <functional>
|
|
#include <tuple>
|
|
#include <type_traits>
|
|
#include <vector>
|
|
#include <concurrent_queue.h>
|
|
|
|
#define _AGENTS_H
|
|
|
|
#pragma pack(push,_CRT_PACKING)
|
|
#pragma warning(push)
|
|
#pragma warning(disable: 4100) // Unreferenced formal parameter - needed for document generation
|
|
#pragma warning(disable: 4702) // Unreachable code - needed for retail version code path
|
|
// Forward declarations
|
|
|
|
/// <summary>
|
|
/// The <c>Concurrency</c> namespace provides classes and functions that provide access to the Concurrency Runtime,
|
|
/// a concurrent programming framework for C++. For more information, see <see cref="Concurrency Runtime"/>.
|
|
/// </summary>
|
|
/**/
|
|
namespace Concurrency
|
|
{
|
|
/// <summary>
|
|
/// Each message instance has an identity that follows it as it is
|
|
/// cloned and passed between messaging components. This cannot be the
|
|
/// address of the message object.
|
|
/// </summary>
|
|
/**/
|
|
typedef __int32 runtime_object_identity;
|
|
|
|
/// <summary>
|
|
/// A lock holder that acquires a non-reentrant lock on instantiation and releases
|
|
/// it on destruction.
|
|
/// </summary>
|
|
/**/
|
|
typedef ::Concurrency::details::_NonReentrantPPLLock::_Scoped_lock _NR_lock;
|
|
|
|
/// <summary>
|
|
/// A lock holder that acquires a reentrant lock on instantiation and releases
|
|
/// it on destruction
|
|
/// </summary>
|
|
/**/
|
|
typedef ::Concurrency::details::_ReentrantPPLLock::_Scoped_lock _R_lock;
|
|
|
|
|
|
//***************************************************************************
|
|
// Internal namespace:
|
|
//
|
|
// Concurrency::details contains definitions to support routines in the public namespaces and macros.
|
|
// Clients should not directly interact with this namespace.
|
|
//***************************************************************************
|
|
|
|
namespace details
|
|
{
|
|
//**************************************************************************
|
|
// Core Messaging Support:
|
|
//**************************************************************************
|
|
|
|
//
|
|
// A base class to derive from that keeps unique IDs on its derived classes
|
|
//
|
|
class _Runtime_object : public _AllocBase
|
|
{
|
|
public:
|
|
// Creates a new runtime object.
|
|
_CRTIMP2 _Runtime_object();
|
|
|
|
// Creates a runtime object from an identity.
|
|
_CRTIMP2 _Runtime_object(::Concurrency::runtime_object_identity _Id);
|
|
|
|
// Gets the runtime object identity.
|
|
virtual ::Concurrency::runtime_object_identity _GetId() const
|
|
{
|
|
return _M_id;
|
|
}
|
|
|
|
protected:
|
|
// The runtime object identity.
|
|
::Concurrency::runtime_object_identity _M_id;
|
|
};
|
|
|
|
// A queue used to hold the messages for the messaging blocks
|
|
template<class _Message>
|
|
class _Queue : public _AllocBase
|
|
{
|
|
protected:
|
|
// A pointer to the head of the queue.
|
|
_Message * _M_pHead;
|
|
|
|
// A pointer to a pointer to the tail of the queue.
|
|
_Message ** _M_ppTail;
|
|
|
|
// The number of elements presently stored in the queue.
|
|
size_t _M_count;
|
|
|
|
public:
|
|
typedef typename _Message type;
|
|
|
|
// Create a Queue
|
|
_Queue() : _M_pHead(NULL), _M_ppTail(&_M_pHead), _M_count(0)
|
|
{
|
|
}
|
|
|
|
// Destroy the queue
|
|
~_Queue()
|
|
{
|
|
}
|
|
|
|
// Returns the count of items in the queue
|
|
size_t _Count() const
|
|
{
|
|
return _M_count;
|
|
}
|
|
|
|
// Add an item to the tail of the queue
|
|
//
|
|
// Returns a Boolean indicating whether the operation succeeded.
|
|
bool _Enqueue(_Message *_Element)
|
|
{
|
|
_CONCRT_ASSERT(_Element->_M_pNext == NULL);
|
|
_CONCRT_ASSERT(*_M_ppTail == NULL);
|
|
|
|
*_M_ppTail = _Element;
|
|
_Element->_M_pNext = NULL;
|
|
_M_ppTail = &(_Element->_M_pNext);
|
|
_M_count++;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Remove the specified element from the queue
|
|
//
|
|
// Returns a Boolean indicating whether the operation succeeded, that is, the message was found in the queue.
|
|
bool _Remove(_Message * _OldElement)
|
|
{
|
|
bool _Result = false;
|
|
|
|
_CONCRT_ASSERT(_OldElement != NULL);
|
|
|
|
if (_M_pHead == _OldElement)
|
|
{
|
|
_M_pHead = _OldElement->_M_pNext;
|
|
if (_M_pHead == NULL)
|
|
{
|
|
_M_ppTail = &_M_pHead;
|
|
}
|
|
|
|
_OldElement->_M_pNext = NULL;
|
|
_M_count--;
|
|
_Result = true;
|
|
}
|
|
else
|
|
{
|
|
_Message * _Next = NULL;
|
|
for (_Message * _Node = _M_pHead; _Node != NULL; _Node = _Next)
|
|
{
|
|
_Next = _Node->_M_pNext;
|
|
|
|
if (_Node->_M_pNext == _OldElement)
|
|
{
|
|
_Node->_M_pNext = _OldElement->_M_pNext;
|
|
// if this is the last element of the _Queue
|
|
if (_Node->_M_pNext == NULL && _M_count == 1)
|
|
{
|
|
_M_ppTail = &_M_pHead;
|
|
}
|
|
|
|
_OldElement->_M_pNext = NULL;
|
|
_M_count--;
|
|
_Result = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
// Dequeue an item from the head of queue
|
|
//
|
|
// Returns a pointer to the message found at the head of the queue.
|
|
_Message * _Dequeue()
|
|
{
|
|
if (_M_pHead == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
_Message * _Result = _M_pHead;
|
|
|
|
_M_pHead = _Result->_M_pNext;
|
|
if (_M_pHead == NULL)
|
|
{
|
|
_M_ppTail = &_M_pHead;
|
|
}
|
|
|
|
_Result->_M_pNext = NULL;
|
|
_M_count--;
|
|
return _Result;
|
|
}
|
|
|
|
// Return the item at the head of the queue, without dequeuing
|
|
//
|
|
// Returns a pointer to the message found at the head of the queue.
|
|
_Message * _Peek()
|
|
{
|
|
return _M_pHead;
|
|
}
|
|
|
|
// Return true if the ID matches the message at the head of the queue
|
|
bool _Is_head(runtime_object_identity _MsgId)
|
|
{
|
|
// Peek at the next message in the message buffer. Use it to
|
|
// check if the IDs match
|
|
_Message * _Msg = _M_pHead;
|
|
|
|
if (_Msg == NULL || _Msg->msg_id() != _MsgId)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
//
|
|
// _Dynamic_array implements a container very similar to std::vector.
|
|
// However, it exposes a reduced subset of functionality that is
|
|
// geared towards use in network_link_registry. The array acess is not
|
|
// thread-safe.
|
|
//
|
|
template<class _Type>
|
|
class _Dynamic_array
|
|
{
|
|
public:
|
|
|
|
typedef _Dynamic_array<_Type> _Myt;
|
|
|
|
typedef _Type& reference;
|
|
typedef _Type const& const_reference;
|
|
|
|
//
|
|
// Construct a dynamic array
|
|
//
|
|
_Dynamic_array()
|
|
{
|
|
_Init();
|
|
}
|
|
|
|
//
|
|
// Release any resources used by dynamic array
|
|
//
|
|
~_Dynamic_array()
|
|
{
|
|
_Clear();
|
|
}
|
|
|
|
//
|
|
// Assignment operator. Copy the contents of _Right
|
|
//
|
|
_Myt& operator=(const _Myt& _Right)
|
|
{
|
|
if (this != &_Right)
|
|
{
|
|
// Remove all the elements
|
|
_Clear();
|
|
|
|
// Allocate space for the new elements
|
|
size_t _Size = _Right._Size();
|
|
_Grow(_Size);
|
|
|
|
// Copy over the new elements
|
|
for (size_t _I=0; _I < _Size; _I++)
|
|
{
|
|
_Push_back(_Right[_I]);
|
|
}
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
//
|
|
// Clear all the elements in the array
|
|
//
|
|
void _Clear()
|
|
{
|
|
if (_M_array != NULL)
|
|
{
|
|
delete [] _M_array;
|
|
_Init();
|
|
}
|
|
}
|
|
|
|
//
|
|
// Add an element to the end of the array
|
|
//
|
|
void _Push_back(_Type const& _Element)
|
|
{
|
|
if (_M_index >= _M_size)
|
|
{
|
|
// Not enough space. Grow the array
|
|
size_t _NewSize = (_M_index + 1) * _S_growthFactor;
|
|
_Grow(_NewSize);
|
|
}
|
|
|
|
_CONCRT_ASSERT(_M_index < _M_size);
|
|
_M_array[_M_index] = _Element;
|
|
_M_index++;
|
|
}
|
|
|
|
//
|
|
// Index operation. Retrieve an element at the specified index. No bounds check is done.
|
|
//
|
|
reference operator[](size_t _Pos)
|
|
{
|
|
_CONCRT_ASSERT(_Pos < _M_size);
|
|
return _M_array[_Pos];
|
|
}
|
|
|
|
//
|
|
// Index operation. Retrieve an element at the specified index. No bounds check is done.
|
|
//
|
|
const_reference operator[](size_t _Pos) const
|
|
{
|
|
_CONCRT_ASSERT(_Pos < _M_size);
|
|
return _M_array[_Pos];
|
|
}
|
|
|
|
//
|
|
// Returns the count of elements in the array
|
|
//
|
|
size_t _Size() const
|
|
{
|
|
return _M_index;
|
|
}
|
|
|
|
//
|
|
// Swap the contents of this array with _Right
|
|
//
|
|
void _Swap(_Myt& _Right)
|
|
{
|
|
if (this != &_Right)
|
|
{
|
|
// Swap the details.
|
|
_Type * _Array = _M_array;
|
|
size_t _Index = _M_index;
|
|
size_t _Size = _M_size;
|
|
|
|
_M_array = _Right._M_array;
|
|
_M_index = _Right._M_index;
|
|
_M_size = _Right._M_size;
|
|
|
|
_Right._M_array = _Array;
|
|
_Right._M_index = _Index;
|
|
_Right._M_size = _Size;
|
|
}
|
|
}
|
|
|
|
private:
|
|
//
|
|
// Initialize the array
|
|
//
|
|
void _Init()
|
|
{
|
|
_M_array = NULL;
|
|
_M_index = 0;
|
|
_M_size = 0;
|
|
}
|
|
|
|
//
|
|
// Grow the array to the given size. The old elements are copied over.
|
|
//
|
|
void _Grow(size_t _NewSize)
|
|
{
|
|
_CONCRT_ASSERT( _NewSize > _M_size );
|
|
|
|
_Type * _Array = new _Type[_NewSize];
|
|
|
|
if (_M_array != NULL)
|
|
{
|
|
// Copy over the elememts
|
|
for (size_t _I = 0; _I < _M_size; _I++)
|
|
{
|
|
_Array[_I] = _M_array[_I];
|
|
}
|
|
|
|
delete [] _M_array;
|
|
}
|
|
|
|
_M_array = _Array;
|
|
_M_size = _NewSize;
|
|
}
|
|
|
|
// Private data members
|
|
|
|
// Array of elements
|
|
_Type * _M_array;
|
|
|
|
// Index where the next element should be inserted
|
|
size_t _M_index;
|
|
|
|
// Capacity of the array.
|
|
size_t _M_size;
|
|
|
|
static const int _S_growthFactor = 2;
|
|
};
|
|
|
|
//
|
|
// Returns an identifier for the given object that could be used
|
|
// in an ETW trace (call to _Trace_agents)
|
|
//
|
|
template <class _Type>
|
|
__int64 _Trace_agents_get_id(_Type * _PObject)
|
|
{
|
|
return reinterpret_cast<__int64>(_PObject);
|
|
}
|
|
|
|
} // namespace details
|
|
|
|
//**************************************************************************
|
|
// Public Namespace:
|
|
//
|
|
// Anything in the Concurrency namespace is intended for direct client consumption.
|
|
//
|
|
//**************************************************************************
|
|
|
|
//
|
|
// Forward declarations:
|
|
//
|
|
template<class _Type> class ISource;
|
|
template<class _Type> class ITarget;
|
|
|
|
//**************************************************************************
|
|
// Network link registry
|
|
//**************************************************************************
|
|
|
|
// Forward declaration for use in the iterator
|
|
template<class _Block> class network_link_registry;
|
|
|
|
/// <summary>
|
|
/// Const iterator for network link registry. Message blocks should use
|
|
/// the link_registry::iterator type for iteration.
|
|
/// </summary>
|
|
/// <typeparam name="_Block">
|
|
/// The network block type
|
|
/// </typeparam>
|
|
/**/
|
|
template<class _Block>
|
|
class _Network_link_iterator
|
|
{
|
|
public:
|
|
|
|
typedef _Network_link_iterator<_Block> _Myt;
|
|
typedef network_link_registry<_Block> _MyContainer;
|
|
|
|
// Element type
|
|
typedef _Block* _EType;
|
|
|
|
// Const iterator - iterator shall not be used to modify the links
|
|
typedef _EType const& const_reference;
|
|
typedef _EType const* const_pointer;
|
|
|
|
/// <summary>
|
|
/// Construct iterator
|
|
/// </summary>
|
|
/**/
|
|
_Network_link_iterator(_MyContainer * _PNetwork_link, size_t _Index) : _M_pNetwork_link(_PNetwork_link), _M_index(_Index), _M_value(NULL)
|
|
{
|
|
_M_pNetwork_link->_Next_index(_M_index);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copy construct an iterator
|
|
/// </summary>
|
|
/**/
|
|
_Network_link_iterator(_Myt const& _Right)
|
|
{
|
|
_M_pNetwork_link = _Right._M_pNetwork_link;
|
|
_M_index = _Right._M_index;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copy assign an iterator
|
|
/// </summary>
|
|
/**/
|
|
_Myt const& operator=(_Myt const& _Right)
|
|
{
|
|
_M_pNetwork_link = _Right._M_pNetwork_link;
|
|
_M_index = _Right._M_index;
|
|
return *this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the object pointed to by the iterator
|
|
/// </summary>
|
|
/// <returns>
|
|
/// Reference to the object pointed to by the iterator
|
|
/// </returns>
|
|
/**/
|
|
const_reference operator*()
|
|
{
|
|
_M_value = _M_pNetwork_link->_Get_element(_M_index);
|
|
return _M_value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a pointer to the class object
|
|
/// </summary>
|
|
/// <returns>
|
|
/// Returns a pointer to the class object
|
|
/// </returns>
|
|
/**/
|
|
const_pointer operator->() const
|
|
{
|
|
return (&**this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pre-increment the iterator to point to the next element
|
|
/// </summary>
|
|
/// <returns>
|
|
/// Reference to the object pointer to by the iterator after
|
|
/// incrementing it
|
|
/// </returns>
|
|
/**/
|
|
_Myt& operator++()
|
|
{
|
|
++_M_index;
|
|
_M_pNetwork_link->_Next_index(_M_index);
|
|
return (*this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Post-increment the iterator to point to the next element
|
|
/// </summary>
|
|
/// <returns>
|
|
/// Reference to the object pointer to by the iterator before
|
|
/// incrementing it
|
|
/// </returns>
|
|
/**/
|
|
_Myt operator++(int)
|
|
{
|
|
_Myt _Tmp = *this;
|
|
++*this;
|
|
return (_Tmp);
|
|
}
|
|
|
|
private:
|
|
|
|
// Pointer to the underlying container (network link registry)
|
|
_MyContainer * _M_pNetwork_link;
|
|
|
|
// Current index
|
|
size_t _M_index;
|
|
|
|
// Current value
|
|
_EType _M_value;
|
|
};
|
|
|
|
/// <summary>
|
|
/// The <c>network_link_registry</c> abstract base class manages the links between source
|
|
/// and target blocks.
|
|
/// </summary>
|
|
/// <typeparam name="_Block">
|
|
/// The block data type being stored in the <c>network_link_registry</c>.
|
|
/// </typeparam>
|
|
/// <remarks>
|
|
/// The <c>network link registry</c> is not safe for concurrent access.
|
|
/// </remarks>
|
|
/// <seealso cref="single_link_registry Class"/>
|
|
/// <seealso cref="multi_link_registry Class"/>
|
|
/**/
|
|
template<class _Block>
|
|
class network_link_registry
|
|
{
|
|
public:
|
|
|
|
/// <summary>
|
|
/// A type that represents the block type stored in the <c>network_link_registry</c> object.
|
|
/// </summary>
|
|
/**/
|
|
typedef typename _Block type;
|
|
|
|
/// <summary>
|
|
/// A type that represents an element pointer stored in the <c>network_link_registry</c> object.
|
|
/// </summary>
|
|
/**/
|
|
typedef _Block * _EType;
|
|
|
|
/// <summary>
|
|
/// A type that provides a reference to a <c>const</c> element stored in a
|
|
/// <c>network_link_registry</c> object for reading and performing const operations.
|
|
/// </summary>
|
|
/**/
|
|
typedef _EType const& const_reference;
|
|
|
|
/// <summary>
|
|
/// A type that provides a pointer to a <c>const</c> element in a
|
|
/// <c>network_link_registry</c> object.
|
|
/// </summary>
|
|
/**/
|
|
typedef _EType const* const_pointer;
|
|
|
|
// Make the iterators friends so that they can access some of the
|
|
// private routines such as _Get_element.
|
|
/**/
|
|
friend class _Network_link_iterator<_Block>;
|
|
|
|
/// <summary>
|
|
/// A type that provides an iterator that can read or modify any element in a
|
|
/// <c>network_link_registry</c> object.
|
|
/// </summary>
|
|
/**/
|
|
typedef _Network_link_iterator<_Block> iterator;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, adds a link to the <c>network_link_registry</c>
|
|
/// object.
|
|
/// </summary>
|
|
/// <param name="_Link">
|
|
/// A pointer to a block to be added.
|
|
/// </param>
|
|
/**/
|
|
virtual void add(_EType _Link) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, removes a specified block from the
|
|
/// <c>network_link_registry</c> object.
|
|
/// </summary>
|
|
/// <param name="_Link">
|
|
/// A pointer to a block to be removed, if found.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the link was found and removed, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/**/
|
|
virtual bool remove(_EType _Link) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, searches the <c>network_link_registry</c> object
|
|
/// for a specified block.
|
|
/// </summary>
|
|
/// <param name="_Link">
|
|
/// A pointer to a block that is being searched for in the <c>network_link_registry</c>
|
|
/// object.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the block was found, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/**/
|
|
virtual bool contains(_EType _Link) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, returns the number of items in the
|
|
/// <c>network_link_registry</c> object.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The number of items in the <c>network_link_registry</c> object.
|
|
/// </returns>
|
|
/**/
|
|
virtual size_t count() = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, returns an iterator to the first element in the
|
|
/// <c>network_link_registry</c> object.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The end state of the iterator is indicated by a <c>NULL</c> link.
|
|
/// </remarks>
|
|
/// <returns>
|
|
/// An iterator addressing the first element in the <c>network_link_registry</c> object.
|
|
/// </returns>
|
|
/**/
|
|
virtual iterator begin() = 0;
|
|
|
|
protected:
|
|
|
|
/// <summary>
|
|
/// Skips empty slots and updates the index to the next
|
|
/// non-empty slot. This is called by the iterator.
|
|
/// </summary>
|
|
/// <param name="_Index">
|
|
/// A reference to the index that is to be updated.
|
|
/// </param>
|
|
/**/
|
|
virtual void _Next_index(size_t& _Index) = 0;
|
|
|
|
/// <summary>
|
|
/// Retrieves the element at the given index. If the index is out of bounds,
|
|
/// <c>NULL</c> is returned. Users need to use the iterator to access the links.
|
|
/// </summary>
|
|
/// <param name="_Index">
|
|
/// Index of the link to be retrieved.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The element in the registry at the index specified by the <paramref name="_Index"/> parameter.
|
|
/// </returns>
|
|
/**/
|
|
virtual _EType _Get_element(size_t _Index) const = 0;
|
|
};
|
|
|
|
/// <summary>
|
|
/// The <c>single_link_registry</c> object is a <c>network_link_registry</c> that manages
|
|
/// only a single source or target block.
|
|
/// </summary>
|
|
/// <typeparam name="_Block">
|
|
/// The block data type being stored in the <c>single_link_registry</c> object.
|
|
/// </typeparam>
|
|
/// <seealso cref="multi_link_registry Class"/>
|
|
/**/
|
|
template<class _Block>
|
|
class single_link_registry : public network_link_registry<_Block>
|
|
{
|
|
public:
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>single_link_registry</c> object.
|
|
/// </summary>
|
|
/**/
|
|
single_link_registry() : _M_connectedLink(NULL)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Destroys the <c>single_link_registry</c> object.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The method throws an <see cref="invalid_operation Class">invalid_operation</see> exception if
|
|
/// it is called before the link is removed.
|
|
/// </remarks>
|
|
/**/
|
|
virtual ~single_link_registry()
|
|
{
|
|
// It is an error to delete link registry with links
|
|
// still present
|
|
if (count() != 0)
|
|
{
|
|
throw invalid_operation("Deleting link registry before removing all the links");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a link to the <c>single_link_registry</c> object.
|
|
/// </summary>
|
|
/// <param name="_Link">
|
|
/// A pointer to a block to be added.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The method throws an <see cref="invalid_link_target Class">invalid_link_target</see> exception
|
|
/// if there is already a link in this registry.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void add(_EType _Link)
|
|
{
|
|
if (_Link == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Only one link can be added.
|
|
if (_M_connectedLink != NULL)
|
|
{
|
|
throw invalid_link_target("_Link");
|
|
}
|
|
|
|
_M_connectedLink = _Link;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a link from the <c>single_link_registry</c> object.
|
|
/// </summary>
|
|
/// <param name="_Link">
|
|
/// A pointer to a block to be removed, if found.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the link was found and removed, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/**/
|
|
virtual bool remove(_EType _Link)
|
|
{
|
|
if ((_Link != NULL) && (_M_connectedLink == _Link))
|
|
{
|
|
_M_connectedLink = NULL;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Searches the <c>single_link_registry</c> object for a specified block.
|
|
/// </summary>
|
|
/// <param name="_Link">
|
|
/// A pointer to a block that is to be searched for in the <c>single_link_registry</c> object.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the link was found, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/**/
|
|
virtual bool contains(_EType _Link)
|
|
{
|
|
return ((_Link != NULL) && (_M_connectedLink == _Link));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Counts the number of items in the <c>single_link_registry</c> object.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The number of items in the <c>single_link_registry</c> object.
|
|
/// </returns>
|
|
/**/
|
|
virtual size_t count()
|
|
{
|
|
return (_M_connectedLink == NULL) ? 0 : 1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an iterator to the first element in the <c>single_link_registry</c> object.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The end state is indicated by a <c>NULL</c> link.
|
|
/// </remarks>
|
|
/// <returns>
|
|
/// An iterator addressing the first element in the <c>single_link_registry</c> object.
|
|
/// </returns>
|
|
/**/
|
|
virtual iterator begin()
|
|
{
|
|
return (iterator(this, 0));
|
|
}
|
|
|
|
protected:
|
|
|
|
/// <summary>
|
|
/// Skips empty slots and updates the index to the next
|
|
/// non-empty slot. This is called by the iterator.
|
|
/// </summary>
|
|
/// <param name="_Index">
|
|
/// A reference to the index that is to be updated.
|
|
/// </param>
|
|
/**/
|
|
virtual void _Next_index(size_t& _Index)
|
|
{
|
|
if (_M_connectedLink == NULL)
|
|
{
|
|
_Index++;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves the element at the given index. If the index is out of bounds,
|
|
/// <c>NULL</c> is returned. Users need to use the iterator to access the links.
|
|
/// </summary>
|
|
/// <param name="_Index">
|
|
/// The index of the link to be retrieved.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The element in the registry at the index specified by the <paramref name="_Index"/> parameter.
|
|
/// </returns>
|
|
/**/
|
|
virtual _EType _Get_element(size_t _Index) const
|
|
{
|
|
if (_Index == 0)
|
|
{
|
|
return _M_connectedLink;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
private:
|
|
|
|
// A single pointer is used to hold the link
|
|
_EType _M_connectedLink;
|
|
};
|
|
|
|
/// <summary>
|
|
/// The <c>multi_link_registry</c> object is a <c>network_link_registry</c> that manages multiple
|
|
/// source blocks or multiple target blocks.
|
|
/// </summary>
|
|
/// <typeparam name="_Block">
|
|
/// The block data type being stored in the <c>multi_link_registry</c> object.
|
|
/// </typeparam>
|
|
/// <seealso cref="single_link_registry Class"/>
|
|
/**/
|
|
template<class _Block>
|
|
class multi_link_registry : public network_link_registry<_Block>
|
|
{
|
|
public:
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>multi_link_registry</c> object.
|
|
/// </summary>
|
|
/**/
|
|
multi_link_registry() : _M_maxLinks(_NOT_SET)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Destroys the <c>multi_link_registry</c> object.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The method throws an <see cref="invalid_operation Class">invalid_operation</see> exception if
|
|
/// called before all links are removed.
|
|
/// </remarks>
|
|
/**/
|
|
virtual ~multi_link_registry()
|
|
{
|
|
// It is an error to delete link registry with links
|
|
// still present
|
|
if (count() != 0)
|
|
{
|
|
throw invalid_operation("Deleting link registry before removing all the links");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets an upper bound on the number of links that the <c>multi_link_registry</c> object
|
|
/// can hold.
|
|
/// </summary>
|
|
/// <param name="_MaxLinks">
|
|
/// The maximum number of links that the <c>multi_link_registry</c> object can hold.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// After a bound is set, unlinking an entry will cause the <c>multi_link_registry</c>
|
|
/// object to enter an immutable state where further calls to <c>add</c> will throw an
|
|
/// <c>invalid_link_target</c> exception.
|
|
/// </remarks>
|
|
/**/
|
|
void set_bound(size_t _MaxLinks)
|
|
{
|
|
_CONCRT_ASSERT(count() == 0);
|
|
_M_maxLinks = _MaxLinks;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a link to the <c>multi_link_registry</c> object.
|
|
/// </summary>
|
|
/// <param name="_Link">
|
|
/// A pointer to a block to be added.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The method throws an <see cref="invalid_link_target Class">invalid_link_target</see> exception if
|
|
/// the link is already present in the registry, or if a bound has already been set with the <c>set_bound</c>
|
|
/// function and a link has since been removed.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void add(_EType _Link)
|
|
{
|
|
if (_Link == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_Add(_Link);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a link from the <c>multi_link_registry</c> object.
|
|
/// </summary>
|
|
/// <param name="_Link">
|
|
/// A pointer to a block to be removed, if found.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the link was found and removed, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/**/
|
|
virtual bool remove(_EType _Link)
|
|
{
|
|
if (_Link == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return (_Remove(_Link));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Searches the <c>multi_link_registry</c> object for a specified block.
|
|
/// </summary>
|
|
/// <param name="_Link">
|
|
/// A pointer to a block that is to be searched for in the <c>multi_link_registry</c> object.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the specified block was found, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/**/
|
|
virtual bool contains(_EType _Link)
|
|
{
|
|
if (_Link == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return (_Find(_Link) < _M_vector._Size());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Counts the number of items in the <c>multi_link_registry</c> object.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The number of items in the <c>multi_link_registry</c> object.
|
|
/// </returns>
|
|
/**/
|
|
virtual size_t count()
|
|
{
|
|
return _Count();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an iterator to the first element in the <c>multi_link_registry</c> object.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The end state is indicated by a <c>NULL</c> link.
|
|
/// </remarks>
|
|
/// <returns>
|
|
/// An iterator addressing the first element in the <c>multi_link_registry</c> object.
|
|
/// </returns>
|
|
/**/
|
|
virtual iterator begin()
|
|
{
|
|
return (iterator(this, 0));
|
|
}
|
|
|
|
protected:
|
|
|
|
/// <summary>
|
|
/// Skips empty slots and updates the index to the next
|
|
/// non-empty slot. This is called by the iterator.
|
|
/// </summary>
|
|
/// <param name="_Index">
|
|
/// A reference to the index that is to be updated.
|
|
/// </param>
|
|
/**/
|
|
virtual void _Next_index(size_t& _Index)
|
|
{
|
|
size_t _Size = _M_vector._Size();
|
|
while (_Index < _Size)
|
|
{
|
|
if (_M_vector[_Index] != NULL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
++_Index;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves the element at the given index. If the index is out of bounds,
|
|
/// <c>NULL</c> is returned. Users need to use the iterator to access the links
|
|
/// </summary>
|
|
/// <param name="_Index">
|
|
/// Index of the link to be retrieved.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The element in the registry at the index specified by the <paramref name="_Index"/> parameter.
|
|
/// </returns>
|
|
/**/
|
|
virtual _EType _Get_element(size_t _Index) const
|
|
{
|
|
if (_Index < _M_vector._Size())
|
|
{
|
|
return _M_vector[_Index];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
private:
|
|
|
|
/// <summary>
|
|
/// Adds a link to the <c>multi_link_registry</c> object.
|
|
/// </summary>
|
|
/// <param name="_Link">
|
|
/// A pointer to a block to be added.
|
|
/// </param>
|
|
/**/
|
|
void _Add(_EType _Link)
|
|
{
|
|
size_t _Size = _M_vector._Size();
|
|
size_t _Insert_pos = 0;
|
|
|
|
_CONCRT_ASSERT(_Link != NULL);
|
|
|
|
// If max links is set, ensure that inserting the new
|
|
// link will not exceed the bound.
|
|
if ((_M_maxLinks != _NOT_SET) && ((_Size+1) > (size_t) _M_maxLinks))
|
|
{
|
|
throw invalid_link_target("_Link");
|
|
}
|
|
|
|
for (size_t _Index = 0; _Index < _Size; _Index++)
|
|
{
|
|
if (_M_vector[_Index] != NULL)
|
|
{
|
|
// We want to find the first NULL entry after all the
|
|
// non-NULL entries.
|
|
_Insert_pos = _Index + 1;
|
|
|
|
// Throw if dupiclate entry is found
|
|
if (_M_vector[_Index] == _Link)
|
|
{
|
|
throw invalid_link_target("_Link");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_Insert_pos < _Size)
|
|
{
|
|
_M_vector[_Insert_pos] = _Link;
|
|
}
|
|
else
|
|
{
|
|
_M_vector._Push_back(_Link);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a link from the <c>multi_link_registry</c>
|
|
/// </summary>
|
|
/// <param name="_Link">
|
|
/// A pointer to a block to be removed, if found.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the specified link was found and removed, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/**/
|
|
bool _Remove(_EType _Link)
|
|
{
|
|
_CONCRT_ASSERT(_Link != NULL);
|
|
|
|
for (size_t _Index = 0; _Index < _M_vector._Size(); _Index++)
|
|
{
|
|
if (_M_vector[_Index] == _Link)
|
|
{
|
|
_M_vector[_Index] = NULL;
|
|
|
|
// If max links is set, prevent new additions to the registry
|
|
if (_M_maxLinks != _NOT_SET && _M_maxLinks > 0)
|
|
{
|
|
// Setting the bound to 0. This causes add to always throw.
|
|
_M_maxLinks = 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Searches the registry for the given link
|
|
/// </summary>
|
|
/// <param name="_Link">
|
|
/// A pointer to a block that is to be searched.
|
|
/// </param>
|
|
/// <returns>
|
|
/// Index of the entry if found.
|
|
/// </returns>
|
|
/**/
|
|
virtual size_t _Find(_EType _Link)
|
|
{
|
|
size_t _Index = 0;
|
|
for (_Index = 0; _Index < _M_vector._Size(); _Index++)
|
|
{
|
|
if (_M_vector[_Index] == _Link)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return _Index;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the count of items in the registry.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The count of items in the registry.
|
|
/// </returns>
|
|
/**/
|
|
size_t _Count() const
|
|
{
|
|
size_t _Count = 0;
|
|
|
|
for (size_t _Index = 0; _Index < _M_vector._Size(); _Index++)
|
|
{
|
|
if (_M_vector[_Index] != NULL)
|
|
{
|
|
_Count++;
|
|
}
|
|
}
|
|
|
|
return _Count;
|
|
}
|
|
|
|
static const size_t _NOT_SET = SIZE_MAX;
|
|
|
|
// Maximum number of links allowed.
|
|
size_t _M_maxLinks;
|
|
|
|
// ::Concurrency::details::_Dynamic_array is used to hold the links
|
|
::Concurrency::details::_Dynamic_array<_EType> _M_vector;
|
|
};
|
|
|
|
// Forward declaration for the iterator
|
|
template<class _LinkRegistry> class source_link_manager;
|
|
|
|
/// <summary>
|
|
/// Const Iterator for referenced link manager.
|
|
/// </summary>
|
|
/// <typeparam name="_LinkRegistry">
|
|
/// The underlying network link registry
|
|
/// </typeparam>
|
|
/**/
|
|
template<class _LinkRegistry>
|
|
class _Source_link_iterator
|
|
{
|
|
public:
|
|
|
|
typedef typename _LinkRegistry::type _Block;
|
|
|
|
typedef _Source_link_iterator<_LinkRegistry> _Myt;
|
|
typedef source_link_manager<_LinkRegistry> _MyContainer;
|
|
|
|
// Element type
|
|
typedef _Block* _EType;
|
|
|
|
// Const iterator - iterator shall not be used to modify the links
|
|
typedef _EType const& const_reference;
|
|
typedef _EType const* const_pointer;
|
|
|
|
/// <summary>
|
|
/// Construct iterator
|
|
/// </summary>
|
|
/**/
|
|
_Source_link_iterator(_MyContainer * _PNetwork_link, size_t _Index) : _M_pNetwork_link(_PNetwork_link), _M_index(_Index), _M_sentinel(NULL)
|
|
{
|
|
// Take a snapshot of the link registry. This will reference the registry.
|
|
_M_pNetwork_link->_To_array(_M_array);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Destruct iterator
|
|
/// </summary>
|
|
/**/
|
|
virtual ~_Source_link_iterator()
|
|
{
|
|
if (_M_pNetwork_link != NULL)
|
|
{
|
|
_M_pNetwork_link->release();
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Copy construct an iterator
|
|
/// </summary>
|
|
/**/
|
|
_Source_link_iterator(_Myt const& _Right)
|
|
{
|
|
_M_pNetwork_link = _Right._M_pNetwork_link;
|
|
_M_index = _Right._M_index;
|
|
_M_array = _Right._M_array;
|
|
|
|
_M_pNetwork_link->reference();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copy assign an iterator
|
|
/// </summary>
|
|
/**/
|
|
_Myt const& operator=(_Myt const& _Right)
|
|
{
|
|
_MyContainer * _OldContainer = _M_pNetwork_link;
|
|
_CONCRT_ASSERT(_OldContainer != NULL);
|
|
|
|
_M_pNetwork_link = _Right._M_pNetwork_link;
|
|
_M_index = _Right._M_index;
|
|
_M_array = _Right._M_array;
|
|
|
|
if (_OldContainer != _M_pNetwork_link)
|
|
{
|
|
_OldContainer->release();
|
|
_M_pNetwork_link->reference();
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the object pointed to by the iterator
|
|
/// </summary>
|
|
/// <returns>
|
|
/// Reference to the object pointed to by the iterator
|
|
/// </returns>
|
|
/**/
|
|
const_reference operator*()
|
|
{
|
|
return _Get(0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a pointer to the class object
|
|
/// </summary>
|
|
/// <returns>
|
|
/// Returns a pointer to the class object
|
|
/// </returns>
|
|
/**/
|
|
const_pointer operator->() const
|
|
{
|
|
return (&**this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Index operation. Retrieve an element at the specified index.
|
|
/// </summary>
|
|
/**/
|
|
const_reference operator[](size_t _Pos) const
|
|
{
|
|
return _Get(_Pos);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pre-increment the iterator to point to the next element
|
|
/// </summary>
|
|
/// <returns>
|
|
/// Reference to the object pointer to by the iterator after incrementing it
|
|
/// </returns>
|
|
/**/
|
|
_Myt& operator++()
|
|
{
|
|
++_M_index;
|
|
return (*this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Post-increment the iterator to point to the next element
|
|
/// </summary>
|
|
/// <returns>
|
|
/// Reference to the object pointer to by the iterator before incrementing it
|
|
/// </returns>
|
|
/**/
|
|
_Myt operator++(int)
|
|
{
|
|
_Myt _Tmp = *this;
|
|
++*this;
|
|
return (_Tmp);
|
|
}
|
|
|
|
private:
|
|
|
|
// Get the element at the given offset.
|
|
const_reference _Get(size_t _Pos) const
|
|
{
|
|
size_t _Index = _M_index + _Pos;
|
|
if (_Index >= _M_array._Size())
|
|
{
|
|
return _M_sentinel;
|
|
}
|
|
|
|
return _M_array[_Index];
|
|
}
|
|
|
|
// Array to hold the snapshot of the link registry
|
|
::Concurrency::details::_Dynamic_array<_EType> _M_array;
|
|
|
|
// Pointer to the underlying container (network link registry)
|
|
_MyContainer * _M_pNetwork_link;
|
|
|
|
// Current index
|
|
size_t _M_index;
|
|
|
|
// Sentinel value to return on bounds overflow
|
|
_EType _M_sentinel;
|
|
};
|
|
|
|
/// <summary>
|
|
/// The <c>source_link_manager</c> object manages messaging block network links
|
|
/// to <c>ISource</c> blocks.
|
|
/// </summary>
|
|
/// <typeparam name="_LinkRegistry">
|
|
/// The network link registry.
|
|
/// </typeparam>
|
|
/// <remarks>
|
|
/// Currently, the source blocks are reference counted. This is a wrapper on a
|
|
/// <c>network_link_registry</c> object that allows concurrent access to the links and
|
|
/// provides the ability to reference the links through callbacks. Message
|
|
/// blocks (<c>target_block</c>s or <c>propagator_block</c>s) should use this class
|
|
/// for their source links.
|
|
/// </remarks>
|
|
/// <seealso cref="single_link_registry Class"/>
|
|
/// <seealso cref="multi_link_registry Class"/>
|
|
/**/
|
|
template<class _LinkRegistry>
|
|
class source_link_manager
|
|
{
|
|
public:
|
|
|
|
/// <summary>
|
|
/// The type of link registry being managed by the <c>source_link_manager</c> object.
|
|
/// </summary>
|
|
/**/
|
|
typedef _LinkRegistry type;
|
|
|
|
/// <summary>
|
|
/// The type of the blocks being managed by the <c>source_link_manager</c> object.
|
|
/// </summary>
|
|
/**/
|
|
typedef typename _LinkRegistry::type _Block;
|
|
|
|
/// <summary>
|
|
/// The method signature for a callback method for this <c>source_link_manager</c> object.
|
|
/// </summary>
|
|
/**/
|
|
typedef std::tr1::function<void(_Block *, bool)> _Callback_method;
|
|
|
|
/// <summary>
|
|
/// A type that represents a pointer to an element stored in the <c>source_link_manager</c> object.
|
|
/// </summary>
|
|
/**/
|
|
typedef _Block * _EType;
|
|
|
|
/// <summary>
|
|
/// A type that provides a reference to a <c>const</c> element stored in a <c>source_link_manager</c> object
|
|
/// for reading and performing const operations.
|
|
/// </summary>
|
|
/**/
|
|
typedef _EType const& const_reference;
|
|
|
|
/// <summary>
|
|
/// A type that provides a pointer to a <c>const</c> element in a <c>source_link_manager</c> object.
|
|
/// </summary>
|
|
/**/
|
|
typedef _EType const* const_pointer;
|
|
|
|
// Iterator
|
|
friend class _Source_link_iterator<_LinkRegistry>;
|
|
|
|
/// <summary>
|
|
/// A type that provides an iterator that can read or modify any element in the
|
|
/// <c>source_link_manager</c> object.
|
|
/// </summary>
|
|
/**/
|
|
typedef _Source_link_iterator<_LinkRegistry> iterator;
|
|
|
|
/// <summary>
|
|
/// A type that provides a reentrant lock for the <c>source_link_manager</c> object.
|
|
/// </summary>
|
|
/**/
|
|
typedef ::Concurrency::details::_ReentrantPPLLock _LockType;
|
|
|
|
/// <summary>
|
|
/// A type that provides a RAII scoped lock holder for a lock.
|
|
/// </summary>
|
|
/**/
|
|
typedef _LockType::_Scoped_lock _LockHolder;
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>source_link_manager</c> object.
|
|
/// </summary>
|
|
/**/
|
|
source_link_manager() : _M_iteratorCount(0), _M_pLinkedTarget(NULL)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Destroys the <c>source_link_manager</c> object.
|
|
/// </summary>
|
|
/**/
|
|
~source_link_manager()
|
|
{
|
|
_CONCRT_ASSERT(_M_pendingRemove._Size() == 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers the target block that holds this <c>source_link_manager</c> object.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// The target block holding this <c>source_link_manager</c> object.
|
|
/// </param>
|
|
/**/
|
|
void register_target_block(_Inout_ ITarget<typename _Block::source_type> * _PTarget)
|
|
{
|
|
_M_pLinkedTarget = _PTarget;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the maximum number of source links that can be added to this
|
|
/// <c>source_link_manager</c> object.
|
|
/// </summary>
|
|
/// <param name="_MaxLinks">
|
|
/// The maximum number of links.
|
|
/// </param>
|
|
/**/
|
|
void set_bound(size_t _MaxLinks)
|
|
{
|
|
_M_links.set_bound(_MaxLinks);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a source link to the <c>source_link_manager</c> object.
|
|
/// </summary>
|
|
/// <param name="_Link">
|
|
/// A pointer to a block to be added.
|
|
/// </param>
|
|
/**/
|
|
void add(_EType _Link)
|
|
{
|
|
if (_Link == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
{
|
|
_LockHolder _Lock(_M_lock);
|
|
_M_links.add(_Link);
|
|
|
|
// We need to add the _Link first and then invoke the
|
|
// callback because _Add could throw.
|
|
|
|
// As soon as the above lock is released, remove would
|
|
// find the link that was added and could unlink it before
|
|
// we are able to invoke the notification below. Keeping an
|
|
// active iterator would prevent that from happening.
|
|
_M_iteratorCount++;
|
|
}
|
|
|
|
// Acquire a reference on this link by the target
|
|
_Link->acquire_ref(_M_pLinkedTarget);
|
|
|
|
// Release the active iterator
|
|
release();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a link from the <c>source_link_manager</c> object.
|
|
/// </summary>
|
|
/// <param name="_Link">
|
|
/// A pointer to a block to be removed, if found.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the link was found and removed, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/**/
|
|
bool remove(_EType _Link)
|
|
{
|
|
bool _Removed = false;
|
|
_EType _RemovedLink = NULL;
|
|
ITarget<typename _Block::source_type> * _LinkedTarget = _M_pLinkedTarget;
|
|
|
|
if (_Link == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
{
|
|
_LockHolder _Lock(_M_lock);
|
|
_Removed = _M_links.remove(_Link);
|
|
|
|
if (!_Removed)
|
|
{
|
|
// No change was made
|
|
return _Removed;
|
|
}
|
|
|
|
if (_M_iteratorCount == 0)
|
|
{
|
|
// Set the removed link to indicate that
|
|
// notification callback needs to be invoked.
|
|
_RemovedLink = _Link;
|
|
}
|
|
else
|
|
{
|
|
// The iterator will complete the pending operation
|
|
_M_pendingRemove._Push_back(_Link);
|
|
}
|
|
}
|
|
|
|
// NOTE: touching "this" pointer is dangerous as soon as the above lock is released
|
|
|
|
// Release the reference for this link
|
|
if (_RemovedLink != NULL)
|
|
{
|
|
_RemovedLink->release_ref(_LinkedTarget);
|
|
}
|
|
|
|
return _Removed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Acquires a reference on the <c>source_link_manager</c> object.
|
|
/// </summary>
|
|
/**/
|
|
void reference()
|
|
{
|
|
_LockHolder _Lock(_M_lock);
|
|
_M_iteratorCount++;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases the reference on the <c>source_link_manager</c> object.
|
|
/// </summary>
|
|
/**/
|
|
void release()
|
|
{
|
|
ITarget<typename _Block::source_type> * _LinkedTarget = _M_pLinkedTarget;
|
|
::Concurrency::details::_Dynamic_array<_EType> _LinksToRemove;
|
|
|
|
{
|
|
_LockHolder _Lock(_M_lock);
|
|
_CONCRT_ASSERT(_M_iteratorCount > 0);
|
|
_M_iteratorCount--;
|
|
|
|
if (_M_iteratorCount == 0)
|
|
{
|
|
if (_M_pendingRemove._Size() > 0)
|
|
{
|
|
// Snap the pending remove list with the lock held
|
|
_M_pendingRemove._Swap(_LinksToRemove);
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: touching "this" pointer is dangerous as soon as the above lock is released
|
|
|
|
// Release the references
|
|
size_t _Size = _LinksToRemove._Size();
|
|
|
|
for (size_t _I=0; _I < _Size; _I++)
|
|
{
|
|
_LinksToRemove[_I]->release_ref(_LinkedTarget);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Searches the <c>network_link_registry</c> within this <c>source_link_manager</c>
|
|
/// object for a specified block.
|
|
/// </summary>
|
|
/// <param name="_Link">
|
|
/// A pointer to a block that is to be searched for in the <c>source_link_manager</c> object.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the specified block was found, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/**/
|
|
bool contains(_EType _Link)
|
|
{
|
|
_LockHolder _Lock(_M_lock);
|
|
return _M_links.contains(_Link);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Counts the number of linked blocks in the <c>source_link_manager</c> object.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The number of linked blocks in the <c>source_link_manager</c> object.
|
|
/// </returns>
|
|
/**/
|
|
size_t count()
|
|
{
|
|
_LockHolder _Lock(_M_lock);
|
|
return _M_links.count();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Returns an iterator to the first element in the <c>source_link_manager</c> object.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The end state of the iterator is indicated by a <c>NULL</c> link.
|
|
/// </remarks>
|
|
/// <returns>
|
|
/// An iterator addressing the first element in the <c>source_link_manager</c> object.
|
|
/// </returns>
|
|
/**/
|
|
iterator begin()
|
|
{
|
|
return (iterator(this, 0));
|
|
}
|
|
|
|
private:
|
|
|
|
// Called by the iterator. This routine takes a snapshot of the links
|
|
// in the registry and copies it to the array provided.
|
|
void _To_array(::Concurrency::details::_Dynamic_array<_EType>& _Array)
|
|
{
|
|
_LockHolder _Lock(_M_lock);
|
|
_M_iteratorCount++;
|
|
|
|
for(_LinkRegistry::iterator _Link = _M_links.begin(); *_Link != NULL; _Link++)
|
|
{
|
|
_Array._Push_back(*_Link);
|
|
}
|
|
}
|
|
|
|
// Internal lock used for synchronization
|
|
_LockType _M_lock;
|
|
|
|
// Count to indicate that an iterator is active
|
|
volatile long _M_iteratorCount;
|
|
|
|
// A vector of all pending link remove operations
|
|
::Concurrency::details::_Dynamic_array<_EType> _M_pendingRemove;
|
|
|
|
// Underlying link registry
|
|
_LinkRegistry _M_links;
|
|
|
|
// Target block holding this source link manager
|
|
ITarget<typename _Block::source_type> * volatile _M_pLinkedTarget;
|
|
};
|
|
|
|
/// <summary>
|
|
/// The valid responses for an offer of a <c>message</c> object to a block.
|
|
/// </summary>
|
|
/**/
|
|
enum message_status
|
|
{
|
|
/// <summary>
|
|
/// The target accepted the message.
|
|
/// </summary>
|
|
/**/
|
|
accepted,
|
|
/// <summary>
|
|
/// The target did not accept the message.
|
|
/// </summary>
|
|
/**/
|
|
declined,
|
|
/// <summary>
|
|
/// The target postponed the message.
|
|
/// </summary>
|
|
/**/
|
|
postponed,
|
|
/// <summary>
|
|
/// The target tried to accept the message, but it was no longer available.
|
|
/// </summary>
|
|
/**/
|
|
missed
|
|
};
|
|
|
|
/// <summary>
|
|
/// The basic message envelope containing the data payload being passed between
|
|
/// messaging blocks.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The data type of the payload within the message.
|
|
/// </typeparam>
|
|
/// <remarks>
|
|
/// For more information, see <see cref="Asynchronous Message Blocks"/>.
|
|
/// </remarks>
|
|
/**/
|
|
template<class _Type>
|
|
class message : public ::Concurrency::details::_Runtime_object
|
|
{
|
|
friend class ::Concurrency::details::_Queue<message<_Type>>;
|
|
|
|
public:
|
|
/// <summary>
|
|
/// Constructs a <c>message</c> object.
|
|
/// </summary>
|
|
/// <param name="_P">
|
|
/// The payload of this message.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The constructor that takes a pointer to a <c>message</c> object as an argument
|
|
/// throws an <see cref="invalid_argument Class">invalid_argument</see> exception
|
|
/// if the parameter <paramref name="_Msg"/> is <c>NULL</c>.
|
|
/// </remarks>
|
|
/**/
|
|
message(_Type const &_P) : payload(_P), _M_pNext(NULL), _M_refCount(0) { }
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>message</c> object.
|
|
/// </summary>
|
|
/// <param name="_P">
|
|
/// The payload of this message.
|
|
/// </param>
|
|
/// <param name="_Id">
|
|
/// The unique ID of this message.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The constructor that takes a pointer to a <c>message</c> object as an argument
|
|
/// throws an <see cref="invalid_argument Class">invalid_argument</see> exception
|
|
/// if the parameter <paramref name="_Msg"/> is <c>NULL</c>.
|
|
/// </remarks>
|
|
/**/
|
|
message(_Type const &_P, runtime_object_identity _Id)
|
|
: ::Concurrency::details::_Runtime_object(_Id), payload(_P), _M_pNext(NULL), _M_refCount(0)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>message</c> object.
|
|
/// </summary>
|
|
/// <param name="_Msg">
|
|
/// A reference or pointer to a <c>message</c> object.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The constructor that takes a pointer to a <c>message</c> object as an argument
|
|
/// throws an <see cref="invalid_argument Class">invalid_argument</see> exception
|
|
/// if the parameter <paramref name="_Msg"/> is <c>NULL</c>.
|
|
/// </remarks>
|
|
/**/
|
|
message(message const & _Msg) : payload(_Msg.payload), _M_pNext(NULL), _M_refCount(0) { }
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>message</c> object.
|
|
/// </summary>
|
|
/// <param name="_Msg">
|
|
/// A reference or pointer to a <c>message</c> object.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This method throws an <see cref="invalid_argument Class">invalid_argument</see> exception
|
|
/// if the parameter <paramref name="_Msg"/> is <c>NULL</c>.
|
|
/// </remarks>
|
|
/**/
|
|
message(_In_ message const * _Msg) : payload((_Msg == NULL) ? NULL : _Msg->payload), _M_pNext(NULL), _M_refCount(0)
|
|
{
|
|
if (_Msg == NULL)
|
|
{
|
|
throw std::invalid_argument("_Msg");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Destroys the <c>message</c> object.
|
|
/// </summary>
|
|
/**/
|
|
virtual ~message() { }
|
|
|
|
/// <summary>
|
|
/// Returns the ID of the <c>message</c> object.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object.
|
|
/// </returns>
|
|
/**/
|
|
runtime_object_identity msg_id() const
|
|
{
|
|
return _M_id;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The payload of the <c>message</c> object.
|
|
/// </summary>
|
|
/**/
|
|
_Type const payload;
|
|
|
|
/// <summary>
|
|
/// Adds to the reference count for the <c>message</c> object. Used for message blocks that
|
|
/// need reference counting to determine message lifetimes.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The new value of the reference count.
|
|
/// </returns>
|
|
/**/
|
|
long add_ref()
|
|
{
|
|
return _InterlockedIncrement(&_M_refCount);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subtracts from the reference count for the <c>message</c> object. Used for message blocks that
|
|
/// need reference counting to determine message lifetimes.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The new value of the reference count.
|
|
/// </returns>
|
|
/**/
|
|
long remove_ref()
|
|
{
|
|
return _InterlockedDecrement(&_M_refCount);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A type alias for <typeparamref name="_Type"/>.
|
|
/// </summary>
|
|
/**/
|
|
typedef typename _Type type;
|
|
|
|
private:
|
|
// The intrusive next pointer used by blocks that need
|
|
// to chain messages it's holding together
|
|
message * _M_pNext;
|
|
|
|
// Avoid warnings about not generating assignment operators.
|
|
message<_Type> const &operator =(message<_Type> const &);
|
|
|
|
// A reference count for the message
|
|
volatile long _M_refCount;
|
|
};
|
|
|
|
//**************************************************************************
|
|
// Message processor:
|
|
//**************************************************************************
|
|
|
|
/// <summary>
|
|
/// The <c>message_processor</c> class is the abstract base class for processing of
|
|
/// <c>message</c> objects. There is no guarantee on the ordering of the messages.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The data type of the payload within messages handled by this <c>message_processor</c> object.
|
|
/// </typeparam>
|
|
/// <seealso cref="ordered_message_processor Class"/>
|
|
/**/
|
|
template<class _Type>
|
|
class message_processor
|
|
{
|
|
public:
|
|
/// <summary>
|
|
/// A type alias for <typeparamref name="_Type"/>.
|
|
/// </summary>
|
|
/**/
|
|
typedef typename _Type type;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, places messages into the block asynchronously.
|
|
/// </summary>
|
|
/// <param name="_Msg">
|
|
/// A <c>message</c> object to send asynchronously.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// Processor implementations should override this method.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void async_send(_Inout_opt_ message<_Type> * _Msg) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, places messages into the block synchronously.
|
|
/// </summary>
|
|
/// <param name="_Msg">
|
|
/// A <c>message</c> object to send synchronously.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// Processor implementations should override this method.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void sync_send(_Inout_opt_ message<_Type> * _Msg) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, waits for all asynchronous operations to complete.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Processor implementations should override this method.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void wait() = 0;
|
|
|
|
protected:
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, performs the forward processing of
|
|
/// messages into the block. Called once every time a new message is added and
|
|
/// the queue is found to be empty.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Message block implementations should override this method.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void process_incoming_message() = 0;
|
|
|
|
/// <summary>
|
|
/// Wrapper for <c>process_incoming_message</c> suitable for use as a argument to
|
|
/// <c>CreateThread</c> and other similar methods.
|
|
/// </summary>
|
|
/// <param name="_Data">
|
|
/// A pointer to a message processor passed as a void pointer.
|
|
/// </param>
|
|
/**/
|
|
static void __cdecl _Process_incoming_message_wrapper(void * _Data)
|
|
{
|
|
message_processor<_Type> * _PMessageProcessor = (message_processor<_Type> *) _Data;
|
|
_PMessageProcessor->process_incoming_message();
|
|
}
|
|
};
|
|
|
|
/// <summary>
|
|
/// An <c>ordered_message_processor</c> is a <c>message_processor</c> that allows message blocks
|
|
/// to process messages in the order they were received.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload type of messages handled by the processor.
|
|
/// </typeparam>
|
|
/**/
|
|
template<class _Type>
|
|
class ordered_message_processor : public message_processor<_Type>
|
|
{
|
|
public:
|
|
/// <summary>
|
|
/// The signature of the callback method invoked while processing messages.
|
|
/// </summary>
|
|
/**/
|
|
typedef std::tr1::function<void(message<_Type> *)> _Handler_method;
|
|
|
|
/// <summary>
|
|
/// The signature of the callback method invoked while propagating messages.
|
|
/// </summary>
|
|
/**/
|
|
typedef std::tr1::function<void(void)> _Propagator_method;
|
|
|
|
/// <summary>
|
|
/// A type alias for <typeparamref name="_Type"/>.
|
|
/// </summary>
|
|
/**/
|
|
typedef _Type type;
|
|
|
|
/// <summary>
|
|
/// Constructs an <c>ordered_message_processor</c> object.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This <c>ordered_message_processor</c> will not schedule asynchronous or synchronous
|
|
/// handlers until the <c>initialize</c> function is called.
|
|
/// </remarks>
|
|
/**/
|
|
ordered_message_processor() :
|
|
_M_queuedDataCount(0),
|
|
_M_stopProcessing(1),
|
|
_M_lwtCount(0),
|
|
_M_pScheduler(NULL),
|
|
_M_pScheduleGroup(NULL),
|
|
_M_handler(nullptr),
|
|
_M_processor(nullptr),
|
|
_M_propagator(nullptr)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Destroys the <c>ordered_message_processor</c> object.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Waits for all outstanding asynchronous operations before destroying the processor.
|
|
/// </remarks>
|
|
/**/
|
|
virtual ~ordered_message_processor()
|
|
{
|
|
wait();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes the <c>ordered_message_processor</c> object with the appropriate
|
|
/// callback function, scheduler and schedule group.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// A pointer to the scheduler to be used for scheduling light-weight tasks.
|
|
/// </param>
|
|
/// <param name="_PScheduleGroup">
|
|
/// A pointer to the schedule group to be used for scheduling light-weight tasks.
|
|
/// </param>
|
|
/// <param name="_Handler">
|
|
/// The handler functor invoked during callback.
|
|
/// </param>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
void initialize(_Inout_opt_ Scheduler * _PScheduler, _Inout_opt_ ScheduleGroup * _PScheduleGroup, _Handler_method const& _Handler)
|
|
{
|
|
_M_pScheduler = _PScheduler;
|
|
_M_pScheduleGroup = _PScheduleGroup;
|
|
_M_handler = _Handler;
|
|
_M_stopProcessing = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize batched message processing
|
|
/// </summary>
|
|
/// <param name="_Processor">
|
|
/// The processor functor invoked during callback.
|
|
/// </param>
|
|
/// <param name="_Propagator">
|
|
/// The propagator functor invoked during callback.
|
|
/// </param>
|
|
virtual void initialize_batched_processing(_Handler_method const& _Processor, _Propagator_method const& _Propagator)
|
|
{
|
|
_M_processor = _Processor;
|
|
_M_propagator = _Propagator;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Synchronously queues up messages and starts a processing task, if this has not been done
|
|
/// already.
|
|
/// </summary>
|
|
/// <param name="_Msg">
|
|
/// A pointer to a message.
|
|
/// </param>
|
|
/**/
|
|
virtual void sync_send(_Inout_opt_ message<_Type> * _Msg)
|
|
{
|
|
if (_M_handler == NULL)
|
|
{
|
|
throw invalid_operation("sync_send called without registering a callback");
|
|
}
|
|
|
|
_Sync_send_helper(_Msg);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously queues up messages and starts a processing task, if this has not been done
|
|
/// already.
|
|
/// </summary>
|
|
/// <param name="_Msg">
|
|
/// A pointer to a message.
|
|
/// </param>
|
|
/**/
|
|
virtual void async_send(_Inout_opt_ message<_Type> * _Msg)
|
|
{
|
|
if (_M_handler == NULL)
|
|
{
|
|
throw invalid_operation("async_send called without registering a callback");
|
|
}
|
|
|
|
//
|
|
// If there is a message to send, enqueue it in the processing queue.
|
|
// async_send can be sent a NULL message if the block wishes to reprocess
|
|
// the messages that are in its queue. For example, an unbounded_buffer
|
|
// that has its head node released after reservation.
|
|
//
|
|
if (_Msg != NULL)
|
|
{
|
|
_M_queuedMessages.push(_Msg);
|
|
}
|
|
|
|
if (_InterlockedIncrement(&_M_queuedDataCount) == 1)
|
|
{
|
|
// Indicate that an LWT is in progress. This will cause the
|
|
// destructor to block.
|
|
_InterlockedIncrement(&_M_lwtCount);
|
|
|
|
if (_M_stopProcessing == 0)
|
|
{
|
|
_CONCRT_ASSERT(_M_lwtCount > 0);
|
|
|
|
_Trace_agents(AGENTS_EVENT_SCHEDULE, ::Concurrency::details::_Trace_agents_get_id(this));
|
|
|
|
TaskProc _Proc = &::Concurrency::ordered_message_processor<_Type>::_Process_incoming_message_wrapper;
|
|
#ifdef _CRT_USE_WINAPI_FAMILY_DESKTOP_APP
|
|
if (_M_pScheduleGroup != NULL)
|
|
{
|
|
_M_pScheduleGroup->ScheduleTask(_Proc, this);
|
|
}
|
|
else if (_M_pScheduler != NULL)
|
|
{
|
|
_M_pScheduler->ScheduleTask(_Proc, this);
|
|
}
|
|
else
|
|
{
|
|
#endif /* _CRT_USE_WINAPI_FAMILY_DESKTOP_APP */
|
|
::Concurrency::details::_CurrentScheduler::_ScheduleTask(_Proc, this);
|
|
#ifdef _CRT_USE_WINAPI_FAMILY_DESKTOP_APP
|
|
}
|
|
#endif /* _CRT_USE_WINAPI_FAMILY_DESKTOP_APP */
|
|
|
|
// The LWT will decrement _M_lwtCount.
|
|
return;
|
|
}
|
|
|
|
// If we get here then no task was scheduled. Decrement LWT count to reflect this fact
|
|
_InterlockedDecrement(&_M_lwtCount);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A processor-specific spin wait used in destructors of message blocks to make sure
|
|
/// that all asynchronous processing tasks have time to finish before destroying the block.
|
|
/// </summary>
|
|
/**/
|
|
virtual void wait()
|
|
{
|
|
// Cease processing of any new messages
|
|
_InterlockedIncrement(&_M_stopProcessing);
|
|
|
|
// This spin makes sure all previously initiated message processings
|
|
// will still process correctly. As soon as this count reaches zero, we can
|
|
// procede with the message block destructor.
|
|
::Concurrency::details::_SpinWaitBackoffNone spinWait(::Concurrency::details::_Context::_Yield);
|
|
while(_M_lwtCount != 0)
|
|
{
|
|
spinWait._SpinOnce();
|
|
}
|
|
|
|
// Synchronize with sync_send
|
|
{
|
|
_NR_lock _Lock(_M_asyncSendLock);
|
|
_Clear_queued_messages();
|
|
}
|
|
|
|
}
|
|
|
|
protected:
|
|
|
|
/// <summary>
|
|
/// The processing function that is called asynchronously. It dequeues messages and begins
|
|
/// processing them.
|
|
/// </summary>
|
|
/**/
|
|
virtual void process_incoming_message()
|
|
{
|
|
_Trace_agents(AGENTS_EVENT_START, ::Concurrency::details::_Trace_agents_get_id(this));
|
|
long _Count = _Process_message_helper();
|
|
_Trace_agents(AGENTS_EVENT_END, ::Concurrency::details::_Trace_agents_get_id(this), _Count);
|
|
|
|
// Indicate that an LWT completed
|
|
_InterlockedDecrement(&_M_lwtCount);
|
|
|
|
// Do not access any members here. If the count goes to
|
|
// 0 as a result of the above decrement, the object
|
|
// could be immediately deleted.
|
|
}
|
|
|
|
private:
|
|
|
|
void _Clear_queued_messages()
|
|
{
|
|
message<_Type> * _Msg = NULL;
|
|
while (_M_queuedMessages.try_pop(_Msg))
|
|
{
|
|
delete _Msg;
|
|
}
|
|
}
|
|
|
|
void _Sync_send_helper(message<_Type> * _Msg)
|
|
{
|
|
_NR_lock _Lock(_M_asyncSendLock);
|
|
|
|
// Message block destructors sets the _M_stopProcessing flag to stop
|
|
// processing any more messages. This is required to guarantee
|
|
// that the destructor's wait_for_async_sends will complete
|
|
if (_M_stopProcessing == 0)
|
|
{
|
|
if (_M_queuedDataCount > 0)
|
|
{
|
|
long _Count = _InterlockedExchange((volatile long *) &_M_queuedDataCount, 0);
|
|
_Invoke_handler(_Count);
|
|
}
|
|
|
|
_Invoke_handler(_Msg);
|
|
}
|
|
else
|
|
{
|
|
// Destructor is running. Do not process the message
|
|
// Delete the msg, if any.
|
|
if (_Msg != NULL)
|
|
{
|
|
delete _Msg;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Helper function to dequeue and process messages to any targets
|
|
long _Process_message_helper()
|
|
{
|
|
_NR_lock _Lock(_M_asyncSendLock);
|
|
|
|
long _Messages_processed = 0;
|
|
|
|
// Do batched processing of messages
|
|
// Read off the number of messages to process in this iteration by snapping a count
|
|
volatile long _Count = _M_queuedDataCount;
|
|
bool _StopProcessing = false;
|
|
|
|
// This count could be 0 if there was both a synchronous and asynchronous
|
|
// send occuring. One of them could have sent all of the messages for the other
|
|
while (_Count > 0)
|
|
{
|
|
// Process _Count number of messages
|
|
_Invoke_handler(_Count);
|
|
_Messages_processed += _Count;
|
|
|
|
// Subtract the count and see if there are new things to process
|
|
volatile long _Orig = _InterlockedExchangeAdd((volatile long *) &_M_queuedDataCount, -_Count);
|
|
_CONCRT_ASSERT(_Orig >= _Count);
|
|
if (_Orig == _Count)
|
|
{
|
|
// Because _Count did not change, we processed everything there is to process
|
|
break;
|
|
}
|
|
|
|
if (_StopProcessing)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// After reading the flag process the currently queued messages
|
|
// Any messages received after we observe this flag (to be set) will not
|
|
// be processed.
|
|
_StopProcessing = (_M_stopProcessing == 0) ? false : true;
|
|
|
|
// Snap the count and try to process more
|
|
_Count = _M_queuedDataCount;
|
|
}
|
|
|
|
return _Messages_processed;
|
|
}
|
|
|
|
// Invoke the handler in the message block for the given
|
|
// count
|
|
void _Invoke_handler(long _Count)
|
|
{
|
|
// Process _Count number of messages
|
|
for(int _I = 0; _I < _Count; _I++)
|
|
{
|
|
message<_Type> * _Msg = NULL;
|
|
_M_queuedMessages.try_pop(_Msg);
|
|
if (_M_processor == NULL)
|
|
{
|
|
// If a processor function does not exist, the message processor is using single
|
|
// message processing rather than batched processing. There should also be no
|
|
// propagator function defined in this case.
|
|
_CONCRT_ASSERT(_M_propagator == NULL);
|
|
_M_handler(_Msg);
|
|
}
|
|
else
|
|
{
|
|
// Use the batched message processing function
|
|
_M_processor(_Msg);
|
|
}
|
|
}
|
|
|
|
// Call the handler which propagates the message(s)
|
|
if (_M_propagator != NULL)
|
|
{
|
|
_M_propagator();
|
|
}
|
|
}
|
|
|
|
// Invoke the message block handler for the given message
|
|
void _Invoke_handler(message<_Type> * _Msg)
|
|
{
|
|
if (_M_processor == NULL)
|
|
{
|
|
// If a processor function does not exist, the message processor is using single
|
|
// message processing rather than batched processing. There should also be no
|
|
// propagator function defined in this case.
|
|
_CONCRT_ASSERT(_M_propagator == NULL);
|
|
_M_handler(_Msg);
|
|
}
|
|
else
|
|
{
|
|
// Use the batched message processing function
|
|
_M_processor(_Msg);
|
|
|
|
// Call the handler which propagates the message(s)
|
|
if (_M_propagator != NULL)
|
|
{
|
|
_M_propagator();
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
/// <summary>
|
|
/// A queue of the messages
|
|
/// </summary>
|
|
/**/
|
|
concurrent_queue<message<_Type> *> _M_queuedMessages;
|
|
|
|
/// <summary>
|
|
/// A lock to use for queueing incoming messages.
|
|
/// </summary>
|
|
/**/
|
|
::Concurrency::details::_NonReentrantPPLLock _M_asyncSendLock;
|
|
|
|
/// <summary>
|
|
/// A count of the current number of messages to process. Used as a flag
|
|
/// to see if a new process message task needs to be created.
|
|
/// </summary>
|
|
/**/
|
|
volatile long _M_queuedDataCount;
|
|
|
|
/// <summary>
|
|
/// The scheduler to process messages on
|
|
/// </summary>
|
|
/**/
|
|
Scheduler * _M_pScheduler;
|
|
|
|
/// <summary>
|
|
/// The schedule group to process messages on
|
|
/// </summary>
|
|
/**/
|
|
ScheduleGroup * _M_pScheduleGroup;
|
|
|
|
/// <summary>
|
|
/// A flag set in the destructor of a block to cease processing of new messages.
|
|
/// This is required to guarantee that _M_queuedDataCount will get to 0 eventually.
|
|
/// </summary>
|
|
/**/
|
|
volatile long _M_stopProcessing;
|
|
|
|
/// <summary>
|
|
/// A counter to indicate the number of outstanding LWTs
|
|
/// </summary>
|
|
/**/
|
|
volatile long _M_lwtCount;
|
|
|
|
/// <summary>
|
|
/// A message handler object which exposes the callback to be invoked
|
|
/// </summary>
|
|
/**/
|
|
_Handler_method _M_handler;
|
|
|
|
/// <summary>
|
|
/// A message processing object which exposes the callback to be invoked
|
|
/// </summary>
|
|
/**/
|
|
_Handler_method _M_processor;
|
|
|
|
/// <summary>
|
|
/// A message propagating object which exposes the callback to be invoked
|
|
/// </summary>
|
|
/**/
|
|
_Propagator_method _M_propagator;
|
|
};
|
|
|
|
/// <summary>
|
|
/// The <c>ITarget</c> class is the interface for all target blocks. Target blocks
|
|
/// consume messages offered to them by <c>ISource</c> blocks.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The data type of the payload within the messages accepted by the target block.
|
|
/// </typeparam>
|
|
/// <remarks>
|
|
/// For more information, see <see cref="Asynchronous Message Blocks"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="ISource Class"/>
|
|
/**/
|
|
template<class _Type>
|
|
class ITarget
|
|
{
|
|
//
|
|
// ISource<T> is a friend class because calls to Source->link_target()
|
|
// and Source->unlink_target() need to call their respective
|
|
// Target->link_source() and Target->unlink_source() on the block they are
|
|
// linking/unlinking. Those functions are private here because we don't
|
|
// want users calling link_source() or unlink_source() directly. link_source/
|
|
// unlink_source don't call respective link_target/unlink_target because an
|
|
// infinite loop would occur.
|
|
//
|
|
friend class ISource<_Type>;
|
|
|
|
public:
|
|
/// <summary>
|
|
/// Destroys the <c>ITarget</c> object.
|
|
/// </summary>
|
|
/**/
|
|
virtual ~ITarget() {}
|
|
|
|
// It is important that calls to propagate do *not* take the same lock on an
|
|
// internal message structure that is used by Consume and the LWT. Doing so could
|
|
// result in a deadlock with the Consume call.
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, asynchronously passes a message from a source block to
|
|
/// this target block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// The method throws an <see cref="invalid_argument Class">invalid_argument</see> exception
|
|
/// if either the <paramref name="_PMessage"/> or <paramref name="_PSource"/> parameter is <c>NULL</c>.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message_status propagate(_Inout_opt_ message<_Type> * _PMessage, _Inout_opt_ ISource<_Type> * _PSource) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, synchronously passes a message to the target block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// The method throws an <see cref="invalid_argument Class">invalid_argument</see> exception
|
|
/// if either the <paramref name="_PMessage"/> or <paramref name="_PSource"/> parameter is <c>NULL</c>.
|
|
/// <para>Using the <c>send</c> method outside of message initiation and to propagate messages
|
|
/// within a network is dangerous and can lead to deadlock.</para>
|
|
/// <para>When <c>send</c> returns, the message has either already been accepted, and transferred into
|
|
/// the target block, or it has been declined by the target.</para>
|
|
/// </remarks>
|
|
/**/
|
|
virtual message_status send(_Inout_ message<_Type> * _PMessage, _Inout_ ISource<_Type> * _PSource) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, returns true or false depending on whether the
|
|
/// message block accepts messages offered by a source that is not linked to it. If the overridden method returns
|
|
/// <c>true</c>, the target cannot postpone an offered message, as consumption of a postponed message
|
|
/// at a later time requires the source to be identified in its sourse link registry.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// <c>true</c> if the block can accept message from a source that is not linked to it
|
|
/// <c>false</c> otherwise.
|
|
/// </returns>
|
|
/**/
|
|
virtual bool supports_anonymous_source()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// A type alias for <typeparamref name="_Type"/>.
|
|
/// </summary>
|
|
/**/
|
|
typedef typename _Type type;
|
|
|
|
/// <summary>
|
|
/// The signature of any method used by the block that returns a <c>bool</c> value to determine
|
|
/// whether an offered message should be accepted.
|
|
/// </summary>
|
|
/**/
|
|
typedef std::tr1::function<bool(_Type const&)> filter_method;
|
|
|
|
protected:
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, links a specified source block to this <c>ITarget</c> block.
|
|
/// </summary>
|
|
/// <param name="_PSource">
|
|
/// The <c>ISource</c> block being linked to this <c>ITarget</c> block.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This function should not be called directly on an <c>ITarget</c> block. Blocks should be connected together
|
|
/// using the <c>link_target</c> method on <c>ISource</c> blocks, which will invoke the <c>link_source</c> method
|
|
/// on the corresponding target.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void link_source(_Inout_ ISource<_Type> * _PSource) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, unlinks a specified source block from this <c>ITarget</c> block.
|
|
/// </summary>
|
|
/// <param name="_PSource">
|
|
/// The <c>ISource</c> block being unlinked from this <c>ITarget</c> block.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This function should not be called directly on an <c>ITarget</c> block. Blocks should be disconnected
|
|
/// using the <c>unlink_target</c> or <c>unlink_targets</c> methods on <c>ISource</c> blocks, which will invoke
|
|
/// the <c>unlink_source</c> method on the corresponding target.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void unlink_source(_Inout_ ISource<_Type> * _PSource) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, unlinks all source blocks from this <c>ITarget</c> block.
|
|
/// </summary>
|
|
/**/
|
|
virtual void unlink_sources() = 0;
|
|
};
|
|
|
|
/// <summary>
|
|
/// The <c>ISource</c> class is the interface for all source blocks. Source blocks
|
|
/// propagate messages to <c>ITarget</c> blocks.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The data type of the payload within the messages produced by the source block.
|
|
/// </typeparam>
|
|
/// <remarks>
|
|
/// For more information, see <see cref="Asynchronous Message Blocks"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="ITarget Class"/>
|
|
/**/
|
|
template<class _Type>
|
|
class ISource
|
|
{
|
|
public:
|
|
/// <summary>
|
|
/// Destroys the <c>ISource</c> object.
|
|
/// </summary>
|
|
/**/
|
|
virtual ~ISource() {}
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, links a target block to this <c>ISource</c> block.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block being linked to this <c>ISource</c> block.
|
|
/// </param>
|
|
/**/
|
|
virtual void link_target(_Inout_ ITarget<_Type> * _PTarget) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, unlinks a target block from this <c>ISource</c> block,
|
|
/// if found to be previously linked.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block being unlinked from this <c>ISource</c> block.
|
|
/// </param>
|
|
/**/
|
|
virtual void unlink_target(_Inout_ ITarget<_Type> * _PTarget) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, unlinks all target blocks from this
|
|
/// <c>ISource</c> block.
|
|
/// </summary>
|
|
/**/
|
|
virtual void unlink_targets() = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, accepts a message that was offered by this <c>ISource</c> block,
|
|
/// transferring ownership to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the offered <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling the <c>accept</c> method.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the message that the caller now has ownership of.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// The <c>accept</c> method is called by a target while a message is being offered by this <c>ISource</c> block.
|
|
/// The message pointer returned may be different from the one passed into the <c>propagate</c> method
|
|
/// of the <c>ITarget</c> block, if this source decides to make a copy of the message.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message<_Type> * accept(runtime_object_identity _MsgId, _Inout_ ITarget<_Type> * _PTarget) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, reserves a message previously offered by this <c>ISource</c> block.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the offered <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling the <c>reserve</c> method.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the message was successfully reserved, <c>false</c> otherwise. Reservations can fail
|
|
/// for many reasons, including: the message was already reserved or accepted by another target, the source could
|
|
/// deny reservations, and so forth.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// After you call <c>reserve</c>, if it succeeds, you must call either <c>consume</c> or <c>release</c>
|
|
/// in order to take or give up possession of the message, respectively.
|
|
/// </remarks>
|
|
/**/
|
|
virtual bool reserve(runtime_object_identity _MsgId, _Inout_ ITarget<_Type> * _PTarget) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, consumes a message previously offered by this <c>ISource</c> block
|
|
/// and successfully reserved by the target, transferring ownership to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the reserved <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling the <c>consume</c> method.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the <c>message</c> object that the caller now has ownership of.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// The <c>consume</c> method is similar to <c>accept</c>, but must always be preceded by a call to <c>reserve</c> that
|
|
/// returned <c>true</c>.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message<_Type> * consume(runtime_object_identity _MsgId, _Inout_ ITarget<_Type> * _PTarget) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, releases a previous successful message reservation.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the reserved <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling the <c>release</c> method.
|
|
/// </param>
|
|
/**/
|
|
virtual void release(runtime_object_identity _MsgId, _Inout_ ITarget<_Type> * _PTarget) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, acquires a reference count on this <c>ISource</c> block, to prevent deletion.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling this method.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This method is called by an <c>ITarget</c> object that is being linked to this source
|
|
/// during the <c>link_target</c> method.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void acquire_ref(_Inout_ ITarget<_Type> * _PTarget) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, releases a reference count on this <c>ISource</c> block.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling this method.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This method is called by an <c>ITarget</c> object that is being unlinked from this source.
|
|
/// The source block is allowed to release any resources reserved for the target block.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void release_ref(_Inout_ ITarget<_Type> * _PTarget) = 0;
|
|
|
|
/// <summary>
|
|
/// A type alias for <typeparamref name="_Type"/>.
|
|
/// </summary>
|
|
/**/
|
|
typedef typename _Type source_type;
|
|
|
|
protected:
|
|
/// <summary>
|
|
/// Links this source to a target.
|
|
/// </summary>
|
|
/// <param name="_PLinkFrom">
|
|
/// A pointer to the target.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This function definition is required because ISource blocks the need to call
|
|
/// Target->link_source(), which is a private memeber of ITarget. ISource is
|
|
/// declared as a friend class, so this is an way for derived classes of ISource
|
|
/// to properly link/unlink their targets during link_target(), unlink_target() and
|
|
/// unlink_targets()
|
|
/// </remarks>
|
|
/**/
|
|
void _Invoke_link_source(ITarget<_Type> * _PLinkFrom)
|
|
{
|
|
_PLinkFrom->link_source(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unlinks this source from a target.
|
|
/// </summary>
|
|
/// <param name="_PUnlinkFrom">
|
|
/// A pointer to the target.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This function definition is required because ISource blocks need to call
|
|
/// Target->unlink_source(), which is a private memeber of ITarget. ISource is
|
|
/// declared as a friend class, so this is an way for derived classes of ISource
|
|
/// to properly link/unlink their targets during link_target(), unlink_target() and
|
|
/// unlink_targets()
|
|
/// </remarks>
|
|
/**/
|
|
void _Invoke_unlink_source(ITarget<_Type> * _PUnlinkFrom)
|
|
{
|
|
_PUnlinkFrom->unlink_source(this);
|
|
}
|
|
};
|
|
|
|
//**************************************************************************
|
|
// Target Block:
|
|
//**************************************************************************
|
|
|
|
/// <summary>
|
|
/// The <c>target_block</c> class is an abstract base class that provides basic link management
|
|
/// functionality and error checking for target only blocks.
|
|
/// </summary>
|
|
/// <typeparam name="_SourceLinkRegistry">
|
|
/// The link registry to be used for holding the source links.
|
|
/// </typeparam>
|
|
/// <typeparam name="_MessageProcessorType">
|
|
/// The processor type for message processing.
|
|
/// </typeparam>
|
|
/// <seealso cref="ITarget Class"/>
|
|
/**/
|
|
template<class _SourceLinkRegistry,
|
|
class _MessageProcessorType = ordered_message_processor<typename _SourceLinkRegistry::type::source_type>>
|
|
class target_block : public ITarget<typename _SourceLinkRegistry::type::source_type>
|
|
{
|
|
public:
|
|
|
|
/// <summary>
|
|
/// The type of the payload for the incoming messages to this <c>target_block</c> object.
|
|
/// </summary>
|
|
/**/
|
|
typedef typename _SourceLinkRegistry::type::source_type _Source_type;
|
|
|
|
/// <summary>
|
|
/// The type of the <c>source_link_manager</c> this <c>target_block</c> object.
|
|
/// </summary>
|
|
/**/
|
|
typedef source_link_manager<_SourceLinkRegistry> _SourceLinkManager;
|
|
|
|
/// <summary>
|
|
/// The type of the iterator for the <c>source_link_manager</c> for this <c>target_block</c> object.
|
|
/// </summary>
|
|
/**/
|
|
typedef typename _SourceLinkManager::iterator source_iterator;
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>target_block</c> object.
|
|
/// </summary>
|
|
/**/
|
|
target_block() : _M_pFilter(NULL), _M_fDeclineMessages(false)
|
|
{
|
|
_Trace_agents(AGENTS_EVENT_CREATE,
|
|
::Concurrency::details::_Trace_agents_get_id(this),
|
|
::Concurrency::details::_Trace_agents_get_id(&_M_messageProcessor));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Destroys the <c>target_block</c> object.
|
|
/// </summary>
|
|
/**/
|
|
virtual ~target_block()
|
|
{
|
|
// All sources should have been unlinked
|
|
_CONCRT_ASSERT(_M_connectedSources.count() == 0);
|
|
delete _M_pFilter;
|
|
|
|
_Trace_agents(AGENTS_EVENT_DESTROY, ::Concurrency::details::_Trace_agents_get_id(this));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously passes a message from a source block to this target block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// <para> The method throws an <see cref="invalid_argument Class">invalid_argument</see> exception
|
|
/// if either the <paramref name="_PMessage"/> or <paramref name="_PSource"/> parameter is <c>NULL</c>.</para>
|
|
/// </remarks>
|
|
/**/
|
|
virtual message_status propagate(_Inout_opt_ message<_Source_type> * _PMessage, _Inout_opt_ ISource<_Source_type> * _PSource)
|
|
{
|
|
// It is important that calls to propagate do *not* take the same lock on the
|
|
// internal structure that is used by <c>consume</c> and the LWT. Doing so could
|
|
// result in a deadlock.
|
|
|
|
if (_PMessage == NULL)
|
|
{
|
|
throw std::invalid_argument("_PMessage");
|
|
}
|
|
|
|
if (_PSource == NULL)
|
|
{
|
|
throw std::invalid_argument("_PSource");
|
|
}
|
|
|
|
if (_M_fDeclineMessages)
|
|
{
|
|
return declined;
|
|
}
|
|
|
|
if (_M_pFilter != NULL && !(*_M_pFilter)(_PMessage->payload))
|
|
{
|
|
return declined;
|
|
}
|
|
|
|
return propagate_message(_PMessage, _PSource);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Synchronously passes a message from a source block to this target block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// The method throws an <see cref="invalid_argument Class">invalid_argument</see> exception
|
|
/// if either the <paramref name="_PMessage"/> or <paramref name="_PSource"/> parameter is <c>NULL</c>.
|
|
/// <para>Using the <c>send</c> method outside of message initiation and to propagate messages
|
|
/// within a network is dangerous and can lead to deadlock.</para>
|
|
/// <para>When <c>send</c> returns, the message has either already been accepted, and transferred into
|
|
/// the target block, or it has been declined by the target.</para>
|
|
/// </remarks>
|
|
/**/
|
|
virtual message_status send(_Inout_ message<_Source_type> * _PMessage, _Inout_ ISource<_Source_type> * _PSource)
|
|
{
|
|
if (_PMessage == NULL)
|
|
{
|
|
throw std::invalid_argument("_PMessage");
|
|
}
|
|
|
|
if (_PSource == NULL)
|
|
{
|
|
throw std::invalid_argument("_PSource");
|
|
}
|
|
|
|
if (_M_fDeclineMessages)
|
|
{
|
|
return declined;
|
|
}
|
|
|
|
if (_M_pFilter != NULL && !(*_M_pFilter)(_PMessage->payload))
|
|
{
|
|
return declined;
|
|
}
|
|
|
|
return send_message(_PMessage, _PSource);
|
|
}
|
|
|
|
protected:
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, this method asynchronously passes a message from an <c>ISource</c>
|
|
/// block to this <c>target_block</c> object. It is invoked by the <c>propagate</c> method, when called by a source block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/**/
|
|
virtual message_status propagate_message(_Inout_ message<_Source_type> * _PMessage, _Inout_ ISource<_Source_type> * _PSource) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, this method synchronously passes a message from an <c>ISource</c>
|
|
/// block to this <c>target_block</c> object. It is invoked by the <c>send</c> method, when called by a source block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// By default, this block returns <c>declined</c> unless overridden by a derived class.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message_status send_message(_Inout_ message<_Source_type> *, _Inout_ ISource<_Source_type> *)
|
|
{
|
|
// By default we do not allow send()
|
|
return declined;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Links a specified source block to this <c>target_block</c> object.
|
|
/// </summary>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the <c>ISource</c> block that is to be linked.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This function should not be called directly on a <c>target_block</c> object. Blocks should be connected together
|
|
/// using the <c>link_target</c> method on <c>ISource</c> blocks, which will invoke the <c>link_source</c> method
|
|
/// on the corresponding target.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void link_source(_Inout_ ISource<_Source_type> * _PSource)
|
|
{
|
|
_M_connectedSources.add(_PSource);
|
|
_Trace_agents(AGENTS_EVENT_LINK,
|
|
::Concurrency::details::_Trace_agents_get_id(_PSource),
|
|
::Concurrency::details::_Trace_agents_get_id(this));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unlinks a specified source block from this <c>target_block</c> object.
|
|
/// </summary>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the <c>ISource</c> block that is to be unlinked.
|
|
/// </param>
|
|
/// This function should not be called directly on n <c>target_block</c> object. Blocks should be disconnected
|
|
/// using the <c>unlink_target</c> or <c>unlink_targets</c> methods on <c>ISource</c> blocks, which will invoke
|
|
/// the <c>unlink_source</c> method on the corresponding target.
|
|
/**/
|
|
virtual void unlink_source(_Inout_ ISource<_Source_type> * _PSource)
|
|
{
|
|
_Trace_agents(AGENTS_EVENT_UNLINK,
|
|
::Concurrency::details::_Trace_agents_get_id(_PSource),
|
|
::Concurrency::details::_Trace_agents_get_id(this));
|
|
|
|
_M_connectedSources.remove(_PSource);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unlinks all source blocks from this <c>target_block</c> object.
|
|
/// </summary>
|
|
/**/
|
|
virtual void unlink_sources()
|
|
{
|
|
for (source_iterator _Iter = _M_connectedSources.begin(); *_Iter != NULL; ++_Iter)
|
|
{
|
|
ISource<_Source_type> * _PSource = *_Iter;
|
|
_PSource->unlink_target(this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, processes a message that was accepted by this <c>target_block</c> object.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the message that is to be handled.
|
|
/// </param>
|
|
/**/
|
|
virtual void process_message(message<_Source_type> *)
|
|
{
|
|
}
|
|
|
|
//
|
|
// Utility routines
|
|
//
|
|
|
|
/// <summary>
|
|
/// Registers a filter method that will be invoked on
|
|
/// every message received.
|
|
/// </summary>
|
|
/// <param name="_Filter">
|
|
/// The filter method.
|
|
/// </param>
|
|
/**/
|
|
void register_filter(filter_method const& _Filter)
|
|
{
|
|
if (_Filter != NULL)
|
|
{
|
|
_M_pFilter = new filter_method(_Filter);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates to the block that new messages should be declined.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This method is called by the destructor to ensure that new messages are declined while destruction is in progress.
|
|
/// </remarks>
|
|
/**/
|
|
void decline_incoming_messages()
|
|
{
|
|
_M_fDeclineMessages = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes the base object. Specifically, the <c>message_processor</c> object needs
|
|
/// to be initialized.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// The scheduler to be used for scheduling tasks.
|
|
/// </param>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The schedule group to be used for scheduling tasks.
|
|
/// </param>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
void initialize_target(_Inout_opt_ Scheduler * _PScheduler = NULL, _Inout_opt_ ScheduleGroup * _PScheduleGroup = NULL)
|
|
{
|
|
// Register a callback with the processor
|
|
_M_messageProcessor.initialize(_PScheduler, _PScheduleGroup,
|
|
// Processing and Propagating function used by ordered_message_processors
|
|
[this](message<_Source_type> * _PMessage)
|
|
{
|
|
// Handle message by calling process_message to maintain CRT100 compatibility
|
|
this->process_message(_PMessage);
|
|
});
|
|
|
|
// Register this target block as the owner of the connected sources
|
|
_M_connectedSources.register_target_block(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enables batched processing for this block.
|
|
/// </summary>
|
|
/**/
|
|
void enable_batched_processing()
|
|
{
|
|
_M_messageProcessor.initialize_batched_processing(
|
|
// Processing function used by CRT110
|
|
[this](message<_Source_type> * _PMessage)
|
|
{
|
|
// Handle message through new process_input_message to use CRT110 batch processing
|
|
this->process_input_messages(_PMessage);
|
|
}, nullptr);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously sends a message for processing.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the message being sent.
|
|
/// </param>
|
|
/**/
|
|
void async_send(_Inout_opt_ message<_Source_type> * _PMessage)
|
|
{
|
|
_M_messageProcessor.async_send(_PMessage);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Synchronously send a message for processing.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the message being sent.
|
|
/// </param>
|
|
/**/
|
|
void sync_send(_Inout_opt_ message<_Source_type> * _PMessage)
|
|
{
|
|
_M_messageProcessor.sync_send(_PMessage);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Waits for all asynchronous propagations to complete.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This method is used by message block destructors to ensure all asynchronous operations
|
|
/// have had time to finish before destroying the block.
|
|
/// </remarks>
|
|
/**/
|
|
void wait_for_async_sends()
|
|
{
|
|
// Decline new messages to ensure that messages are not dropped during the wait
|
|
decline_incoming_messages();
|
|
|
|
_M_messageProcessor.wait();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unlinks all sources after waiting for outstanding asynchronous send operations to complete.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// All target blocks should call this routine to remove the sources in their destructor.
|
|
/// </remarks>
|
|
/**/
|
|
void remove_sources()
|
|
{
|
|
wait_for_async_sends();
|
|
|
|
unlink_sources();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes messages that are received as inputs.
|
|
/// </summary>
|
|
/**/
|
|
virtual void process_input_messages(_Inout_ message<_Source_type> * _PMessage)
|
|
{
|
|
throw invalid_operation("To use batched processing, you must override process_input_messages in the message block.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// The container for all the sources connected to this block.
|
|
/// </summary>
|
|
/**/
|
|
_SourceLinkManager _M_connectedSources;
|
|
|
|
/// <summary>
|
|
/// The filter function which determines whether offered messages should be accepted.
|
|
/// </summary>
|
|
/**/
|
|
filter_method * _M_pFilter;
|
|
|
|
/// <summary>
|
|
/// A <c>bool</c> that is set to indicate that all messages should be declined
|
|
/// in preparation for deleting the block
|
|
/// <summary>
|
|
/**/
|
|
bool _M_fDeclineMessages;
|
|
|
|
/// <summary>
|
|
/// The <c>message_processor</c> for this <c>target_block</c>.
|
|
/// <summary>
|
|
/**/
|
|
_MessageProcessorType _M_messageProcessor;
|
|
};
|
|
|
|
//**************************************************************************
|
|
// Source Block:
|
|
//**************************************************************************
|
|
|
|
/// <summary>
|
|
/// The <c>source_block</c> class is an abstract base class for source-only blocks. The class
|
|
/// provides basic link management functionality as well as common error checks.
|
|
/// </summary>
|
|
/// <typeparam name="_TargetLinkRegistry">
|
|
/// Link registry to be used for holding the target links.
|
|
/// </typeparam>
|
|
/// <typeparam name="_MessageProcessorType">
|
|
/// Processor type for message processing.
|
|
/// </typeparam>
|
|
/// <remarks>
|
|
/// Message blocks should derive from this block to take advantage of link management and
|
|
/// synchronization provided by this class.
|
|
/// </remarks>
|
|
/// <seealso cref="ISource Class"/>
|
|
/**/
|
|
template<class _TargetLinkRegistry,
|
|
class _MessageProcessorType = ordered_message_processor<typename _TargetLinkRegistry::type::type>>
|
|
class source_block : public ISource<typename _TargetLinkRegistry::type::type>
|
|
{
|
|
public:
|
|
|
|
/// <summary>
|
|
/// The payload type of messages handled by this <c>source_block</c>.
|
|
/// </summary>
|
|
/**/
|
|
typedef typename _TargetLinkRegistry::type::type _Target_type;
|
|
|
|
/// <summary>
|
|
/// The iterator to walk the connected targets.
|
|
/// </summary>
|
|
/**/
|
|
typedef typename _TargetLinkRegistry::iterator target_iterator;
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>source_block</c> object.
|
|
/// </summary>
|
|
/**/
|
|
source_block() :
|
|
_M_pReservedFor(NULL),
|
|
_M_reservedId(-1),
|
|
_M_referenceCount(0)
|
|
{
|
|
_Trace_agents(AGENTS_EVENT_CREATE,
|
|
::Concurrency::details::_Trace_agents_get_id(this),
|
|
::Concurrency::details::_Trace_agents_get_id(&_M_messageProcessor));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Destroys the <c>source_block</c> object.
|
|
/// </summary>
|
|
/**/
|
|
virtual ~source_block()
|
|
{
|
|
// All targets should have been unlinked
|
|
_CONCRT_ASSERT(_M_connectedTargets.count() == 0);
|
|
|
|
_Trace_agents(AGENTS_EVENT_DESTROY, ::Concurrency::details::_Trace_agents_get_id(this));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Links a target block to this <c>source_block</c> object.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to an <c>ITarget</c> block to link to this <c>source_block</c> object.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The method throws an <see cref="invalid_argument Class">invalid_argument</see> exception if the
|
|
/// parameter <paramref name="_PTarget"/> is <c>NULL</c>.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void link_target(_Inout_ ITarget<_Target_type> * _PTarget)
|
|
{
|
|
_R_lock _Lock(_M_internalLock);
|
|
|
|
if (_PTarget == NULL)
|
|
{
|
|
throw std::invalid_argument("_PTarget");
|
|
}
|
|
|
|
_M_connectedTargets.add(_PTarget);
|
|
_Invoke_link_source(_PTarget);
|
|
link_target_notification(_PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unlinks a target block from this <c>source_block</c> object.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to an <c>ITarget</c> block to unlink from this <c>source_block</c> object.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The method throws an <see cref="invalid_argument Class">invalid_argument</see> exception if the
|
|
/// parameter <paramref name="_PTarget"/> is <c>NULL</c>.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void unlink_target(_Inout_ ITarget<_Target_type> * _PTarget)
|
|
{
|
|
_R_lock _Lock(_M_internalLock);
|
|
|
|
if (_PTarget == NULL)
|
|
{
|
|
throw std::invalid_argument("_PTarget");
|
|
}
|
|
|
|
if (_M_connectedTargets.remove(_PTarget))
|
|
{
|
|
// We were able to remove the target from our list.
|
|
// Inform the target to unlink from us
|
|
_Invoke_unlink_source(_PTarget);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unlinks all target blocks from this <c>source_block</c> object.
|
|
/// </summary>
|
|
/**/
|
|
virtual void unlink_targets()
|
|
{
|
|
_R_lock _Lock(_M_internalLock);
|
|
|
|
for (target_iterator _Iter = _M_connectedTargets.begin(); *_Iter != NULL; ++_Iter)
|
|
{
|
|
ITarget<_Target_type> * _PTarget = *_Iter;
|
|
_CONCRT_ASSERT(_PTarget != NULL);
|
|
|
|
unlink_target(_PTarget);
|
|
}
|
|
|
|
// All the targets should be unlinked.
|
|
_CONCRT_ASSERT(_M_connectedTargets.count() == 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accepts a message that was offered by this <c>source_block</c> object, transferring ownership to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the offered <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling the <c>accept</c> method.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the <c>message</c> object that the caller now has ownership of.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// The method throws an <see cref="invalid_argument Class">invalid_argument</see> exception if the
|
|
/// parameter <paramref name="_PTarget"/> is <c>NULL</c>.
|
|
/// <para>
|
|
/// The <c>accept</c> method is called by a target while a message is being offered by this <c>ISource</c> block.
|
|
/// The message pointer returned may be different from the one passed into the <c>propagate</c> method
|
|
/// of the <c>ITarget</c> block, if this source decides to make a copy of the message.
|
|
/// </para>
|
|
/// </remarks>
|
|
/**/
|
|
virtual message<_Target_type> * accept(runtime_object_identity _MsgId, _Inout_ ITarget<_Target_type> * _PTarget)
|
|
{
|
|
if (_PTarget == NULL)
|
|
{
|
|
throw std::invalid_argument("_PTarget");
|
|
}
|
|
|
|
// Assert if the target is not connected
|
|
_CONCRT_ASSERT(_M_connectedTargets.contains(_PTarget));
|
|
|
|
return accept_message(_MsgId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reserves a message previously offered by this <c>source_block</c> object.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the offered <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling the <c>reserve</c> method.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the message was successfully reserved, <c>false</c> otherwise. Reservations can fail
|
|
/// for many reasons, including: the message was already reserved or accepted by another target, the source could
|
|
/// deny reservations, and so forth.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// The method throws an <see cref="invalid_argument Class">invalid_argument</see> exception if the
|
|
/// parameter <paramref name="_PTarget"/> is <c>NULL</c>.
|
|
/// <para>
|
|
/// After you call <c>reserve</c>, if it succeeds, you must call either <c>consume</c> or <c>release</c>
|
|
/// in order to take or give up possession of the message, respectively.
|
|
/// </para>
|
|
/// </remarks>
|
|
/**/
|
|
virtual bool reserve(runtime_object_identity _MsgId, _Inout_ ITarget<_Target_type> * _PTarget)
|
|
{
|
|
_R_lock _Lock(_M_internalLock);
|
|
|
|
if (_PTarget == NULL)
|
|
{
|
|
throw std::invalid_argument("_PTarget");
|
|
}
|
|
|
|
if ( _M_pReservedFor != NULL)
|
|
{
|
|
// Someone else is holding the reservation
|
|
return false;
|
|
}
|
|
|
|
if (!reserve_message(_MsgId))
|
|
{
|
|
// Failed to reserve the msg ID
|
|
return false;
|
|
}
|
|
|
|
// Save the reserving target and the msg ID
|
|
_M_pReservedFor = _PTarget;
|
|
_M_reservedId = _MsgId;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Consumes a message previously offered by this <c>source_block</c> object and successfully reserved by the target,
|
|
/// transferring ownership to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the reserved <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling the <c>consume</c> method.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the <c>message</c> object that the caller now has ownership of.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// The method throws an <see cref="invalid_argument Class">invalid_argument</see> exception if the
|
|
/// parameter <paramref name="_PTarget"/> is <c>NULL</c>.
|
|
/// </para>
|
|
/// <para>
|
|
/// The method throws a <see cref="bad_target Class">bad_target</see> exception if the parameter <paramref name="_PTarget"/>
|
|
/// does not represent the target that called <c>reserve</c>.
|
|
/// </para>
|
|
/// <para>
|
|
/// The <c>consume</c> method is similar to <c>accept</c>, but must always be preceded by a call to <c>reserve</c> that
|
|
/// returned <c>true</c>.
|
|
/// </para>
|
|
/// </remarks>
|
|
/**/
|
|
virtual message<_Target_type> * consume(runtime_object_identity _MsgId, _Inout_ ITarget<_Target_type> * _PTarget)
|
|
{
|
|
_R_lock _Lock(_M_internalLock);
|
|
|
|
if (_PTarget == NULL)
|
|
{
|
|
throw std::invalid_argument("_PTarget");
|
|
}
|
|
|
|
if (_M_pReservedFor == NULL || _PTarget != _M_pReservedFor)
|
|
{
|
|
throw bad_target();
|
|
}
|
|
|
|
message<_Target_type> * _Msg = consume_message(_MsgId);
|
|
|
|
if (_Msg != NULL)
|
|
{
|
|
// Clear the reservation
|
|
// _M_pReservedId is intentionally not reset so that it can assist in debugging
|
|
_M_pReservedFor = NULL;
|
|
|
|
// Reservation is assumed to block propagation. Notify that propagation can now be resumed
|
|
resume_propagation();
|
|
}
|
|
|
|
return _Msg;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases a previous successful message reservation.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the reserved <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling the <c>release</c> method.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// The method throws an <see cref="invalid_argument Class">invalid_argument</see> exception if the
|
|
/// parameter <paramref name="_PTarget"/> is <c>NULL</c>.
|
|
/// </para>
|
|
/// <para>
|
|
/// The method throws a <see cref="bad_target Class">bad_target</see> exception if the parameter <paramref name="_PTarget"/>
|
|
/// does not represent the target that called <c>reserve</c>.
|
|
/// </para>
|
|
/// </remarks>
|
|
/**/
|
|
virtual void release(runtime_object_identity _MsgId, _Inout_ ITarget<_Target_type> * _PTarget)
|
|
{
|
|
_R_lock _Lock(_M_internalLock);
|
|
|
|
if (_PTarget == NULL)
|
|
{
|
|
throw std::invalid_argument("_PTarget");
|
|
}
|
|
|
|
if (_PTarget != _M_pReservedFor)
|
|
{
|
|
throw bad_target();
|
|
}
|
|
|
|
release_message(_MsgId);
|
|
|
|
// Clear the reservation
|
|
// _M_pReservedId is intentionally not reset so that it can assist in debugging
|
|
_M_pReservedFor = NULL;
|
|
|
|
// Reservation is assumed to block propagation. Notify that propagation can now be resumed
|
|
resume_propagation();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Acquires a reference count on this <c>source_block</c> object, to prevent deletion.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling this method.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This method is called by an <c>ITarget</c> object that is being linked to this source
|
|
/// during the <c>link_target</c> method.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void acquire_ref(_Inout_ ITarget<_Target_type> *)
|
|
{
|
|
_InterlockedIncrement(&_M_referenceCount);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases a reference count on this <c>source_block</c> object.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling this method.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This method is called by an <c>ITarget</c> object that is being unlinked from this source.
|
|
/// The source block is allowed to release any resources reserved for the target block.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void release_ref(_Inout_ ITarget<_Target_type> * _PTarget)
|
|
{
|
|
if (_PTarget != NULL)
|
|
{
|
|
_R_lock _Lock(_M_internalLock);
|
|
|
|
// We assume that each target would keep a single reference on its source, so
|
|
// we call unlink target notification on every release. Otherwise, we would be
|
|
// required to keep a reference count per target.
|
|
// Note: unlink_target_notification can check the value of this _PTarget pointer, but
|
|
// must not dereference it, as it may have already been deleted.
|
|
unlink_target_notification(_PTarget);
|
|
}
|
|
|
|
_InterlockedDecrement(&_M_referenceCount);
|
|
|
|
// It is *unsafe* to touch the "this" pointer after decrementing the reference count
|
|
}
|
|
|
|
protected:
|
|
|
|
//
|
|
// Protected methods that a derived class can override to customize
|
|
// the functionality
|
|
//
|
|
|
|
/// <summary>
|
|
/// A callback that notifies that a new target has been linked to this <c>source_block</c> object.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// The <c>ITarget</c> block that was linked.
|
|
/// </param>
|
|
/**/
|
|
virtual void link_target_notification(_Inout_ ITarget<_Target_type> *)
|
|
{
|
|
// By default, we restart propagation if there is no pending resrvation
|
|
if (_M_pReservedFor == NULL)
|
|
{
|
|
propagate_to_any_targets(NULL);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A callback that notifies that a target has been unlinked from this <c>source_block</c> object.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// The <c>ITarget</c> block that was unlinked.
|
|
/// </param>
|
|
/**/
|
|
virtual void unlink_target_notification(_Inout_ ITarget<_Target_type> * _PTarget)
|
|
{
|
|
// At this point, the target has already been disconnected from the
|
|
// source. It is safe to check the value of this pointer, but not
|
|
// safe to dereference it, as it may have already been deleted.
|
|
|
|
// If the target being unlinked is the one holding the reservation,
|
|
// release the reservation
|
|
if (_M_pReservedFor == _PTarget)
|
|
{
|
|
release(_M_reservedId, _PTarget);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, accepts an offered message by the source.
|
|
/// Message blocks should override this method to validate the <paramref name="_MsgId"/> and
|
|
/// return a message.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The runtime object identity of the <c>message</c> object.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the message that the caller now has ownership of.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// To transfer ownership, the original message pointer should be returned. To maintain
|
|
/// ownership, a copy of message payload needs to be made and returned.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message<_Target_type> * accept_message(runtime_object_identity _MsgId) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, reserves a message previously offered by this
|
|
/// <c>source_block</c> object.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being reserved.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the message was successfully reserved, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// After <c>reserve</c> is called, if it returns <c>true</c>, either <c>consume</c> or <c>release</c> must be called
|
|
/// to either take or release ownership of the message.
|
|
/// </remarks>
|
|
/**/
|
|
virtual bool reserve_message(runtime_object_identity _MsgId) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, consumes a message that was previously reserved.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being consumed.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the message that the caller now has ownership of.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// Similar to <c>accept</c>, but is always preceded by a call to <c>reserve</c>.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message<_Target_type> * consume_message(runtime_object_identity _MsgId) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, releases a previous message reservation.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being released.
|
|
/// </param>
|
|
/**/
|
|
virtual void release_message(runtime_object_identity _MsgId) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, resumes propagation after a reservation has been released.
|
|
/// </summary>
|
|
/**/
|
|
virtual void resume_propagation() = 0;
|
|
|
|
/// <summary>
|
|
/// Process input messages. This is only useful for propagator blocks, which derive from source_block
|
|
/// </summary>
|
|
/**/
|
|
virtual void process_input_messages(_Inout_ message<_Target_type> * _PMessage)
|
|
{
|
|
// source_blocks do not need to process anything
|
|
}
|
|
|
|
/// <summary>
|
|
/// Propagate messages to targets.
|
|
/// </summary>
|
|
/**/
|
|
virtual void propagate_output_messages()
|
|
{
|
|
throw invalid_operation("To use batched processing, you must override propagate_output_messages in the message block.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, propagates the given message to any or all of the linked targets.
|
|
/// This is the main propagation routine for message blocks.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the message that is to be propagated.
|
|
/// </param>
|
|
/**/
|
|
virtual void propagate_to_any_targets(_Inout_opt_ message<_Target_type> * _PMessage)
|
|
{
|
|
throw invalid_operation("To use ordered message processing, you must override propagate_to_any_targets in the message block.");
|
|
}
|
|
|
|
//
|
|
// Utility routines
|
|
//
|
|
/// <summary>
|
|
/// Initializes the <c>message_propagator</c> within this <c>source_block</c>.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// The scheduler to be used for scheduling tasks.
|
|
/// </param>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The schedule group to be used for scheduling tasks.
|
|
/// </param>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
void initialize_source(_Inout_opt_ Scheduler * _PScheduler = NULL, _Inout_opt_ ScheduleGroup * _PScheduleGroup = NULL)
|
|
{
|
|
// Register a callback
|
|
_M_messageProcessor.initialize(_PScheduler, _PScheduleGroup,
|
|
[this](message<_Target_type> * _PMessage)
|
|
{
|
|
this->_Handle_message(_PMessage);
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enables batched processing for this block.
|
|
/// </summary>
|
|
/**/
|
|
void enable_batched_processing()
|
|
{
|
|
// Register callbacks for CRT110 batched processing
|
|
_M_messageProcessor.initialize_batched_processing(
|
|
// Processing function used by CRT110
|
|
[this](message<_Target_type> * _PMessage)
|
|
{
|
|
// Handle message through new process_input_message to use CRT110 batch processing
|
|
this->process_input_messages(_PMessage);
|
|
},
|
|
[this](void)
|
|
{
|
|
this->_Propagate_message();
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Synchronously queues up messages and starts a propagation task, if this has not been done
|
|
/// already.
|
|
/// </summary>
|
|
/// <param name="_Msg">
|
|
/// A pointer to a <c>message</c> object to synchronously send.
|
|
/// </param>
|
|
/**/
|
|
virtual void sync_send(_Inout_opt_ message<_Target_type> * _Msg)
|
|
{
|
|
// Caller shall not be holding any locks when calling this routine
|
|
_M_messageProcessor.sync_send(_Msg);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously queues up messages and starts a propagation task, if this has not been done
|
|
/// already
|
|
/// </summary>
|
|
/// <param name="_Msg">
|
|
/// A pointer to a <c>message</c> object to asynchronously send.
|
|
/// </param>
|
|
/**/
|
|
virtual void async_send(_Inout_opt_ message<_Target_type> * _Msg)
|
|
{
|
|
_M_messageProcessor.async_send(_Msg);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Waits for all asynchronous propagations to complete. This propagator-specific spin wait is used
|
|
/// in destructors of message blocks to make sure that all asynchronous propagations have time to finish
|
|
/// before destroying the block.
|
|
/// </summary>
|
|
/**/
|
|
void wait_for_outstanding_async_sends()
|
|
{
|
|
_M_messageProcessor.wait();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes all target links for this source block. This should be called from the destructor.
|
|
/// </summary>
|
|
/**/
|
|
void remove_targets()
|
|
{
|
|
// Wait for outstanding propagation to complete.
|
|
wait_for_outstanding_async_sends();
|
|
|
|
unlink_targets();
|
|
|
|
_Wait_on_ref();
|
|
}
|
|
|
|
//
|
|
// Protected members
|
|
//
|
|
|
|
/// <summary>
|
|
/// Connected target that is holding a reservation
|
|
/// </summary>
|
|
/**/
|
|
ITarget<_Target_type> * _M_pReservedFor;
|
|
|
|
/// <summary>
|
|
/// Reserved message ID
|
|
/// </summary>
|
|
/**/
|
|
runtime_object_identity _M_reservedId;
|
|
|
|
/// <summary>
|
|
/// Connected targets
|
|
/// </summary>
|
|
/**/
|
|
_TargetLinkRegistry _M_connectedTargets;
|
|
|
|
/// <summary>
|
|
/// Processor used for asynchronous message handling
|
|
/// </summary>
|
|
/**/
|
|
_MessageProcessorType _M_messageProcessor;
|
|
|
|
private:
|
|
|
|
/// Private methods
|
|
|
|
|
|
// Message handler callback for the propagator. Invokes propagate_to_any_targets
|
|
// which derived classes should implement.
|
|
/**/
|
|
void _Handle_message(message<_Target_type> * _PMessage)
|
|
{
|
|
// Hold a lock to synchronize with unlink targets
|
|
_R_lock _Lock(_M_internalLock);
|
|
propagate_to_any_targets(_PMessage);
|
|
}
|
|
|
|
// Message handler callback for the processor. Invokes process_input_messages
|
|
// which derived classes should implement.
|
|
/**/
|
|
void _Process_message(message<_Target_type> * _PMessage)
|
|
{
|
|
// Don't need a lock to process the message
|
|
process_input_messages(_PMessage);
|
|
}
|
|
|
|
// Message handler callback for the propagator. Invokes propagate_output_messages
|
|
// which derived classes should implement.
|
|
/**/
|
|
void _Propagate_message()
|
|
{
|
|
// Hold a lock to synchronize with unlink targets
|
|
_R_lock _Lock(_M_internalLock);
|
|
propagate_output_messages();
|
|
}
|
|
|
|
// Wait for the reference on this block to drop to zero
|
|
/**/
|
|
void _Wait_on_ref(long _RefCount = 0)
|
|
{
|
|
::Concurrency::details::_SpinWaitBackoffNone spinWait;
|
|
while(_M_referenceCount != _RefCount)
|
|
{
|
|
spinWait._SpinOnce();
|
|
}
|
|
}
|
|
|
|
// Private Data members
|
|
|
|
/// <summary>
|
|
/// Internal lock used for the following synchronization:
|
|
/// 1. Synchronize between link and unlink target
|
|
/// 2. Synchronize between propagate_to_any_targets and unlink_target
|
|
/// 3. Synchronize between reserve and consume/release
|
|
/// </summary>
|
|
/**/
|
|
::Concurrency::details::_ReentrantPPLLock _M_internalLock;
|
|
|
|
volatile long _M_referenceCount;
|
|
|
|
};
|
|
|
|
//**************************************************************************
|
|
// Propagator (source and target) Block:
|
|
//**************************************************************************
|
|
/// <summary>
|
|
/// The <c>propagator_block</c> class is an abstract base class for message blocks that are both a source and target.
|
|
/// It combines the functionality of both the <c>source_block</c> and <c>target_block</c> classes.
|
|
/// </summary>
|
|
/// <typeparam name="_TargetLinkRegistry">
|
|
/// The link registry to be used for holding the target links.
|
|
/// </typeparam>
|
|
/// <typeparam name="_SourceLinkRegistry">
|
|
/// The link registry to be used for holding the source links.
|
|
/// </typeparam>
|
|
/// <typeparam name="_MessageProcessorType">
|
|
/// The processor type for message processing.
|
|
/// </typeparam>
|
|
/// <remarks>
|
|
/// To avoid multiple inheritance, the <c>propagator_block</c> class inherits from the <c>source_block</c> class and <c>ITarget</c>
|
|
/// abstract class. Most of the functionality in the <c>target_block</c> class is replicated here.
|
|
/// </remarks>
|
|
/// <seealso cref="source_block Class"/>
|
|
/// <seealso cref="ITarget Class"/>
|
|
/**/
|
|
template<class _TargetLinkRegistry, class _SourceLinkRegistry,
|
|
class _MessageProcessorType = ordered_message_processor<typename _TargetLinkRegistry::type::type>>
|
|
class propagator_block : public source_block<_TargetLinkRegistry, _MessageProcessorType>, public ITarget<typename _SourceLinkRegistry::type::source_type>
|
|
{
|
|
public:
|
|
|
|
/// <summary>
|
|
/// The type of the payload for the incoming message to this <c>propagator_block</c>.
|
|
/// </summary>
|
|
/**/
|
|
typedef typename _SourceLinkRegistry::type::source_type _Source_type;
|
|
|
|
/// <summary>
|
|
/// The type of the <c>source_link_manager</c> this <c>propagator_block</c>.
|
|
/// </summary>
|
|
/**/
|
|
typedef source_link_manager<_SourceLinkRegistry> _SourceLinkManager;
|
|
|
|
/// <summary>
|
|
/// The type of the iterator for the <c>source_link_manager</c> for this <c>propagator_block</c>.
|
|
/// </summary>
|
|
/**/
|
|
typedef typename _SourceLinkManager::iterator source_iterator;
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>propagator_block</c> object.
|
|
/// </summary>
|
|
/**/
|
|
propagator_block() : _M_pFilter(NULL), _M_fDeclineMessages(false)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Destroys a <c>propagator_block</c> object.
|
|
/// </summary>
|
|
/**/
|
|
virtual ~propagator_block()
|
|
{
|
|
remove_network_links();
|
|
|
|
delete _M_pFilter;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously passes a message from a source block to this target block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// The <c>propagate</c> method is invoked on a target block by a linked source block. It queues up an
|
|
/// asynchronous task to handle the message, if one is not already queued or executing.
|
|
/// <para> The method throws an <see cref="invalid_argument Class">invalid_argument</see> exception
|
|
/// if either the <paramref name="_PMessage"/> or <paramref name="_PSource"/> parameter is <c>NULL</c>.</para>
|
|
/// </remarks>
|
|
/**/
|
|
virtual message_status propagate(_Inout_opt_ message<_Source_type> * _PMessage, _Inout_opt_ ISource<_Source_type> * _PSource)
|
|
{
|
|
// It is important that calls to propagate do *not* take the same lock on the
|
|
// internal structure that is used by <c>consume</c> and the LWT. Doing so could
|
|
// result in a deadlock.
|
|
|
|
if (_PMessage == NULL)
|
|
{
|
|
throw std::invalid_argument("_PMessage");
|
|
}
|
|
|
|
if (_PSource == NULL)
|
|
{
|
|
throw std::invalid_argument("_PSource");
|
|
}
|
|
|
|
if (_M_fDeclineMessages)
|
|
{
|
|
return declined;
|
|
}
|
|
|
|
if (_M_pFilter != NULL && !(*_M_pFilter)(_PMessage->payload))
|
|
{
|
|
return declined;
|
|
}
|
|
|
|
return propagate_message(_PMessage, _PSource);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Synchronously initiates a message to this block. Called by an <c>ISource</c> block.
|
|
/// When this function completes, the message will already have propagated into the block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// This method throws an <see cref="invalid_argument Class">invalid_argument</see> exception if either
|
|
/// the <paramref name="_PMessage"/> or <paramref name="_PSource"/> parameter is <c>NULL</c>.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message_status send(_Inout_ message<_Source_type> * _PMessage, _Inout_ ISource<_Source_type> * _PSource)
|
|
{
|
|
if (_PMessage == NULL)
|
|
{
|
|
throw std::invalid_argument("_PMessage");
|
|
}
|
|
|
|
if (_PSource == NULL)
|
|
{
|
|
throw std::invalid_argument("_PSource");
|
|
}
|
|
|
|
if (_M_fDeclineMessages)
|
|
{
|
|
return declined;
|
|
}
|
|
|
|
if (_M_pFilter != NULL && !(*_M_pFilter)(_PMessage->payload))
|
|
{
|
|
return declined;
|
|
}
|
|
|
|
return send_message(_PMessage, _PSource);
|
|
}
|
|
|
|
protected:
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, this method asynchronously passes a message from an <c>ISource</c>
|
|
/// block to this <c>propagator_block</c> object. It is invoked by the <c>propagate</c> method, when called by a source block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/**/
|
|
virtual message_status propagate_message(_Inout_ message<_Source_type> * _PMessage, _Inout_ ISource<_Source_type> * _PSource) = 0;
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, this method synchronously passes a message from an <c>ISource</c>
|
|
/// block to this <c>propagator_block</c> object. It is invoked by the <c>send</c> method, when called by a source block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// By default, this block returns <c>declined</c> unless overridden by a derived class.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message_status send_message(_Inout_ message<_Source_type> *, _Inout_ ISource<_Source_type> *)
|
|
{
|
|
// By default we do not allow send()
|
|
return declined;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Links a specified source block to this <c>propagator_block</c> object.
|
|
/// </summary>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the <c>ISource</c> block that is to be linked.
|
|
/// </param>
|
|
/**/
|
|
virtual void link_source(_Inout_ ISource<_Source_type> * _PSource)
|
|
{
|
|
_M_connectedSources.add(_PSource);
|
|
_Trace_agents(AGENTS_EVENT_LINK,
|
|
::Concurrency::details::_Trace_agents_get_id(_PSource),
|
|
::Concurrency::details::_Trace_agents_get_id(this));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unlinks a specified source block from this <c>propagator_block</c> object.
|
|
/// </summary>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the <c>ISource</c> block that is to be unlinked.
|
|
/// </param>
|
|
/**/
|
|
virtual void unlink_source(_Inout_ ISource<_Source_type> * _PSource)
|
|
{
|
|
_Trace_agents(AGENTS_EVENT_UNLINK,
|
|
::Concurrency::details::_Trace_agents_get_id(_PSource),
|
|
::Concurrency::details::_Trace_agents_get_id(this));
|
|
|
|
_M_connectedSources.remove(_PSource);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unlinks all source blocks from this <c>propagator_block</c> object.
|
|
/// </summary>
|
|
/**/
|
|
virtual void unlink_sources()
|
|
{
|
|
for (source_iterator _Iter = _M_connectedSources.begin(); *_Iter != NULL; ++_Iter)
|
|
{
|
|
ISource<_Source_type> * _PSource = *_Iter;
|
|
_PSource->unlink_target(this);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Utility routines
|
|
//
|
|
|
|
/// <summary>
|
|
/// Process input messages. This is only useful for propagator blocks, which derive from source_block
|
|
/// </summary>
|
|
/**/
|
|
virtual void process_input_messages(_Inout_ message<_Target_type> * _PMessage)
|
|
{
|
|
throw invalid_operation("To use batched processing, you must override process_input_messages in the message block.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes the base object. Specifically, the <c>message_processor</c> object needs
|
|
/// to be initialized.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// The scheduler to be used for scheduling tasks.
|
|
/// </param>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The schedule group to be used for scheduling tasks.
|
|
/// </param>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
void initialize_source_and_target(_Inout_opt_ Scheduler * _PScheduler = NULL, _Inout_opt_ ScheduleGroup * _PScheduleGroup = NULL)
|
|
{
|
|
initialize_source(_PScheduler, _PScheduleGroup);
|
|
|
|
// Register this propagator block as the owner of the connected sources
|
|
_M_connectedSources.register_target_block(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers a filter method that will be invoked on every received message.
|
|
/// </summary>
|
|
/// <param name="_Filter">
|
|
/// The filter method.
|
|
/// </param>
|
|
/**/
|
|
void register_filter(filter_method const& _Filter)
|
|
{
|
|
if (_Filter != NULL)
|
|
{
|
|
_M_pFilter = new filter_method(_Filter);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates to the block that new messages should be declined.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This method is called by the destructor to ensure that new messages are declined while destruction is in progress.
|
|
/// </remarks>
|
|
/**/
|
|
void decline_incoming_messages()
|
|
{
|
|
_M_fDeclineMessages = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes all the source and target network links from this <c>propagator_block</c> object.
|
|
/// </summary>
|
|
/**/
|
|
void remove_network_links()
|
|
{
|
|
// Decline messages while the links are being removed
|
|
decline_incoming_messages();
|
|
|
|
// Remove all the target links. This waits for
|
|
// all outstanding async propagation operations.
|
|
remove_targets();
|
|
|
|
// unlink all sources. The above steps guarantee that
|
|
// they can be removed safely.
|
|
unlink_sources();
|
|
}
|
|
|
|
/// <summary>
|
|
/// The container for all the sources connected to this block.
|
|
/// </summary>
|
|
/**/
|
|
_SourceLinkManager _M_connectedSources;
|
|
|
|
/// <summary>
|
|
/// The filter function which determines whether offered messages should be accepted.
|
|
/// </summary>
|
|
/**/
|
|
filter_method * _M_pFilter;
|
|
|
|
/// <summary>
|
|
/// A <c>bool</c> that is set to indicate that all messages should be declined
|
|
/// in preparation for deleting the block
|
|
/// <summary>
|
|
/**/
|
|
volatile bool _M_fDeclineMessages;
|
|
};
|
|
|
|
//**************************************************************************
|
|
// Unbounded Buffers:
|
|
//**************************************************************************
|
|
|
|
/// <summary>
|
|
/// An <c>unbounded_buffer</c> messaging block is a multi-target, multi-source, ordered
|
|
/// <c>propagator_block</c> capable of storing an unbounded number of messages.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload type of the messages stored and propagated by the buffer.
|
|
/// </typeparam>
|
|
/// <remarks>
|
|
/// For more information, see <see cref="Asynchronous Message Blocks"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="overwrite_buffer Class"/>
|
|
/// <seealso cref="single_assignment Class"/>
|
|
/**/
|
|
template<class _Type>
|
|
class unbounded_buffer : public propagator_block<multi_link_registry<ITarget<_Type>>, multi_link_registry<ISource<_Type>>>
|
|
{
|
|
public:
|
|
/// <summary>
|
|
/// Constructs an <c>unbounded_buffer</c> messaging block.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>unbounded_buffer</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
unbounded_buffer() :
|
|
_M_fForceRepropagation(false)
|
|
{
|
|
initialize_source_and_target();
|
|
enable_batched_processing();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs an <c>unbounded_buffer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_Filter">
|
|
/// A filter function which determines whether offered messages should be accepted.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>unbounded_buffer</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
unbounded_buffer(filter_method const& _Filter) :
|
|
_M_fForceRepropagation(false)
|
|
{
|
|
initialize_source_and_target();
|
|
enable_batched_processing();
|
|
register_filter(_Filter);
|
|
}
|
|
|
|
#ifdef _CRT_USE_WINAPI_FAMILY_DESKTOP_APP
|
|
/// <summary>
|
|
/// Constructs an <c>unbounded_buffer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// The <c>Scheduler</c> object within which the propagation task for the <c>unbounded_buffer</c> object is scheduled.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>unbounded_buffer</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
unbounded_buffer(Scheduler& _PScheduler) :
|
|
_M_fForceRepropagation(false)
|
|
{
|
|
initialize_source_and_target(&_PScheduler);
|
|
enable_batched_processing();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs an <c>unbounded_buffer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// The <c>Scheduler</c> object within which the propagation task for the <c>unbounded_buffer</c> messaging block is scheduled.
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A filter function which determines whether offered messages should be accepted.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>unbounded_buffer</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
unbounded_buffer(Scheduler& _PScheduler, filter_method const& _Filter) :
|
|
_M_fForceRepropagation(false)
|
|
{
|
|
initialize_source_and_target(&_PScheduler);
|
|
enable_batched_processing();
|
|
register_filter(_Filter);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs an <c>unbounded_buffer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The <c>ScheduleGroup</c> object within which the propagation task for the <c>unbounded_buffer</c> messaging block is scheduled.
|
|
/// The <c>Scheduler</c> object used is implied by the schedule group.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>unbounded_buffer</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
unbounded_buffer(ScheduleGroup& _PScheduleGroup) :
|
|
_M_fForceRepropagation(false)
|
|
{
|
|
initialize_source_and_target(NULL, &_PScheduleGroup);
|
|
enable_batched_processing();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs an <c>unbounded_buffer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The <c>ScheduleGroup</c> object within which the propagation task for the <c>unbounded_buffer</c> messaging block is scheduled.
|
|
/// The <c>Scheduler</c> object used is implied by the schedule group.
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A filter function which determines whether offered messages should be accepted.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>unbounded_buffer</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
unbounded_buffer(ScheduleGroup& _PScheduleGroup, filter_method const& _Filter) :
|
|
_M_fForceRepropagation(false)
|
|
{
|
|
initialize_source_and_target(NULL, &_PScheduleGroup);
|
|
enable_batched_processing();
|
|
register_filter(_Filter);
|
|
}
|
|
#endif /* _CRT_USE_WINAPI_FAMILY_DESKTOP_APP */
|
|
|
|
/// <summary>
|
|
/// Destroys the <c>unbounded_buffer</c> messaging block.
|
|
/// </summary>
|
|
/**/
|
|
~unbounded_buffer()
|
|
{
|
|
// Remove all links
|
|
remove_network_links();
|
|
|
|
// Clean up any messages left in this message block
|
|
_Delete_stored_messages();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds an item to the <c>unbounded_buffer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_Item">
|
|
/// The item to add.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the item was accepted, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/**/
|
|
bool enqueue(_Type const& _Item)
|
|
{
|
|
return Concurrency::send<_Type>(this, _Item);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes an item from the <c>unbounded_buffer</c> messaging block.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The payload of the message removed from the <c>unbounded_buffer</c>.
|
|
/// </returns>
|
|
/**/
|
|
_Type dequeue()
|
|
{
|
|
return receive<_Type>(this);
|
|
}
|
|
|
|
|
|
protected:
|
|
|
|
//
|
|
// propagator_block protected function implementations
|
|
//
|
|
|
|
/// <summary>
|
|
/// Asynchronously passes a message from an <c>ISource</c> block to this <c>unbounded_buffer</c> messaging block.
|
|
/// It is invoked by the <c>propagate</c> method, when called by a source block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/**/
|
|
virtual message_status propagate_message(_Inout_ message<_Type> * _PMessage, _Inout_ ISource<_Type> * _PSource)
|
|
{
|
|
// It is important that calls to propagate do *not* take the same lock on the
|
|
// internal structure that is used by <c>consume</c> and the LWT. Doing so could
|
|
// result in a deadlock.
|
|
|
|
message_status _Result = accepted;
|
|
|
|
// Accept the message being propagated
|
|
_PMessage = _PSource->accept(_PMessage->msg_id(), this);
|
|
|
|
if (_PMessage != NULL)
|
|
{
|
|
async_send(_PMessage);
|
|
}
|
|
else
|
|
{
|
|
_Result = missed;
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Synchronously passes a message from an <c>ISource</c> block to this <c>unbounded_buffer</c> messaging block.
|
|
/// It is invoked by the <c>send</c> method, when called by a source block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/**/
|
|
virtual message_status send_message(_Inout_ message<_Type> * _PMessage, _Inout_ ISource<_Type> * _PSource)
|
|
{
|
|
_PMessage = _PSource->accept(_PMessage->msg_id(), this);
|
|
|
|
if (_PMessage != NULL)
|
|
{
|
|
sync_send(_PMessage);
|
|
}
|
|
else
|
|
{
|
|
return missed;
|
|
}
|
|
|
|
return accepted;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Overrides the <c>supports_anonymous_source</c> method to indicate that this block can
|
|
/// accept messages offered to it by a source that is not linked.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// <c>true</c> because the block does not postpone offered messages.
|
|
/// </returns>
|
|
/**/
|
|
virtual bool supports_anonymous_source()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accepts a message that was offered by this <c>unbounded_buffer</c> messaging block,
|
|
/// transferring ownership to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the offered <c>message</c> object.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the <c>message</c> object that the caller now has ownership of.
|
|
/// </returns>
|
|
/**/
|
|
virtual message<_Type> * accept_message(runtime_object_identity _MsgId)
|
|
{
|
|
//
|
|
// Peek at the head message in the message buffer. If the IDs match
|
|
// dequeue and transfer ownership
|
|
//
|
|
message<_Type> * _Msg = NULL;
|
|
|
|
if (_M_messageBuffer._Is_head(_MsgId))
|
|
{
|
|
_Msg = _M_messageBuffer._Dequeue();
|
|
}
|
|
|
|
return _Msg;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reserves a message previously offered by this <c>unbounded_buffer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being reserved.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the message was successfully reserved, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// After <c>reserve</c> is called, if it returns <c>true</c>, either <c>consume</c> or <c>release</c> must be called
|
|
/// to either take or release ownership of the message.
|
|
/// </remarks>
|
|
/**/
|
|
virtual bool reserve_message(runtime_object_identity _MsgId)
|
|
{
|
|
// Allow reservation if this is the head message
|
|
return _M_messageBuffer._Is_head(_MsgId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Consumes a message previously offered by the <c>unbounded_buffer</c> messaging block and reserved by the target,
|
|
/// transferring ownership to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being consumed.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the <c>message</c> object that the caller now has ownership of.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// Similar to <c>accept</c>, but is always preceded by a call to <c>reserve</c>.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message<_Type> * consume_message(runtime_object_identity _MsgId)
|
|
{
|
|
// By default, accept the message
|
|
return accept_message(_MsgId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases a previous message reservation.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being released.
|
|
/// </param>
|
|
/**/
|
|
virtual void release_message(runtime_object_identity _MsgId)
|
|
{
|
|
// The head message is the one reserved.
|
|
if (!_M_messageBuffer._Is_head(_MsgId))
|
|
{
|
|
throw message_not_found();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resumes propagation after a reservation has been released.
|
|
/// </summary>
|
|
/**/
|
|
virtual void resume_propagation()
|
|
{
|
|
// If there are any messages in the buffer, propagate them out
|
|
if (_M_messageBuffer._Count() > 0)
|
|
{
|
|
// Set the flag to force a repropagation. This flag is cleared when a propagation happens
|
|
// The only functions that call this are release, consume, and link_target, all of which
|
|
// hold the internal lock, so the flag is guaranteed to be read by propagation, which also
|
|
// holds the same lock.
|
|
_M_fForceRepropagation = true;
|
|
|
|
// async send a NULL value to initiate the repropagation
|
|
async_send(NULL);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A callback that notifies that a new target has been linked to this <c>unbounded_buffer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the newly linked target.
|
|
/// </param>
|
|
/**/
|
|
virtual void link_target_notification(_Inout_ ITarget<_Type> * _PTarget)
|
|
{
|
|
// If the message queue is blocked due to reservation
|
|
// there is no need to do any message propagation
|
|
if (_M_pReservedFor != NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
message<_Type> * _Msg = _M_messageBuffer._Peek();
|
|
|
|
if (_Msg != NULL)
|
|
{
|
|
// Propagate the head message to the new target
|
|
message_status _Status = _PTarget->propagate(_Msg, this);
|
|
|
|
if (_Status == accepted)
|
|
{
|
|
// The target accepted the message, restart propagation.
|
|
_Propagate_priority_order(_M_messageBuffer);
|
|
}
|
|
|
|
// If the status is anything other than accepted, then leave
|
|
// the message queue blocked.
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Places the <c>message</c> <paramref name="_PMessage"/> in this <c>unbounded_buffer</c> messaging block and
|
|
/// tries to offer it to all of the linked targets.
|
|
/// </summary>
|
|
virtual void process_input_messages(_Inout_ message<_Type> * _PMessage)
|
|
{
|
|
if (_PMessage != NULL)
|
|
{
|
|
_M_processedMessages._Enqueue(_PMessage);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Places the <c>message</c> <paramref name="_PMessage"/> in this <c>unbounded_buffer</c> messaging block and
|
|
/// tries to offer it to all of the linked targets.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to a <c>message</c> object that this <c>unbounded_buffer</c> has taken ownership of.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// If another message is already ahead of this one in the <c>unbounded_buffer</c>,
|
|
/// propagation to linked targets will not occur until any earlier messages have been accepted
|
|
/// or consumed. The first linked target to successfully <c>accept</c> or <c>consume</c> the
|
|
/// message takes ownership, and no other target can then get the message.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void propagate_output_messages()
|
|
{
|
|
// Move the messages from the processedMessages queue to the internal storage
|
|
// to make them ready for propagating out
|
|
|
|
// If there are messages in the message queue, the queue is blocked and a
|
|
// propagation should not happen unless it has been forced using resume_propagation
|
|
bool _FIsBlocked = (_M_messageBuffer._Count() > 0);
|
|
|
|
for(;;)
|
|
{
|
|
message<_Type> * _PInputMessage = _M_processedMessages._Dequeue();
|
|
if(_PInputMessage == NULL)
|
|
{
|
|
break;
|
|
}
|
|
_M_messageBuffer._Enqueue(_PInputMessage);
|
|
}
|
|
|
|
if (_M_fForceRepropagation == false && _FIsBlocked == true)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Reset the repropagation flag because a propagation has started.
|
|
_M_fForceRepropagation = false;
|
|
|
|
// Attempt to propagate messages to all the targets
|
|
_Propagate_priority_order(_M_messageBuffer);
|
|
}
|
|
|
|
private:
|
|
|
|
/// <summary>
|
|
/// Propagates messages in priority order.
|
|
/// </summary>
|
|
/// <param name="_MessageBuffer">
|
|
/// Reference to a message queue with messages to be propagated
|
|
/// </param>
|
|
/**/
|
|
void _Propagate_priority_order(::Concurrency::details::_Queue<message<_Target_type>> & _MessageBuffer)
|
|
{
|
|
message<_Target_type> * _Msg = _MessageBuffer._Peek();
|
|
|
|
// If someone has reserved the _Head message, don't propagate anymore
|
|
if (_M_pReservedFor != NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
while (_Msg != NULL)
|
|
{
|
|
message_status _Status = declined;
|
|
|
|
// Always start from the first target that linked
|
|
for (target_iterator _Iter = _M_connectedTargets.begin(); *_Iter != NULL; ++_Iter)
|
|
{
|
|
ITarget<_Target_type> * _PTarget = *_Iter;
|
|
_Status = _PTarget->propagate(_Msg, this);
|
|
|
|
// Ownership of message changed. Do not propagate this
|
|
// message to any other target.
|
|
if (_Status == accepted)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// If the target just propagated to reserved this message, stop
|
|
// propagating it to others
|
|
if (_M_pReservedFor != NULL)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If status is anything other than accepted, then the head message
|
|
// was not propagated out. Thus, nothing after it in the queue can
|
|
// be propagated out. Cease propagation.
|
|
if (_Status != accepted)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Get the next message
|
|
_Msg = _MessageBuffer._Peek();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes all messages currently stored in this message block. Should be called
|
|
/// by the destructor to ensure any messages propagated in are cleaned up.
|
|
/// </summary>
|
|
/**/
|
|
void _Delete_stored_messages()
|
|
{
|
|
// Input messages for this message block are in the base-class input buffer
|
|
// All messages in that buffer are guaranteed to have moved to the output
|
|
// buffer because the destructor first waits for all async sends to finish
|
|
// before reaching this point
|
|
|
|
// Delete any messages remaining in the output queue
|
|
for (;;)
|
|
{
|
|
message<_Type> * _Msg = _M_messageBuffer._Dequeue();
|
|
if (_Msg == NULL)
|
|
{
|
|
break;
|
|
}
|
|
delete _Msg;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Message queue used to store processed messages
|
|
/// </summary>
|
|
/**/
|
|
::Concurrency::details::_Queue<message<_Type>> _M_processedMessages;
|
|
|
|
/// <summary>
|
|
/// Message queue used to store messages
|
|
/// </summary>
|
|
/**/
|
|
::Concurrency::details::_Queue<message<_Type>> _M_messageBuffer;
|
|
|
|
/// <summary>
|
|
/// A bool to signal to the processor to force a repropagation to occur
|
|
/// </summary>
|
|
/**/
|
|
bool _M_fForceRepropagation;
|
|
|
|
private:
|
|
//
|
|
// Hide assignment operator and copy constructor
|
|
//
|
|
unbounded_buffer const &operator =(unbounded_buffer const&); // no assignment operator
|
|
unbounded_buffer(unbounded_buffer const &); // no copy constructor
|
|
};
|
|
|
|
//**************************************************************************
|
|
// Overwrite Buffers:
|
|
//**************************************************************************
|
|
|
|
/// <summary>
|
|
/// An <c>overwrite_buffer</c> messaging block is a multi-target, multi-source, ordered
|
|
/// <c>propagator_block</c> capable of storing a single message at
|
|
/// a time. New messages overwrite previously held ones.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload type of the messages stored and propagated by the buffer.
|
|
/// </typeparam>
|
|
/// <remarks>
|
|
/// An <c>overwrite_buffer</c> messaging block propagates out copies of its stored message to each of its targets.
|
|
/// <para>For more information, see <see cref="Asynchronous Message Blocks"/>.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="unbounded_buffer Class"/>
|
|
/// <seealso cref="single_assignment Class"/>
|
|
/**/
|
|
template<class _Type>
|
|
class overwrite_buffer : public propagator_block<multi_link_registry<ITarget<_Type>>, multi_link_registry<ISource<_Type>>>
|
|
{
|
|
public:
|
|
/// <summary>
|
|
/// Constructs an <c>overwrite_buffer</c> messaging block.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>overwrite_buffer</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
overwrite_buffer() :
|
|
_M_fIsInitialized(false), _M_pMessage(NULL), _M_pReservedMessage(NULL)
|
|
{
|
|
initialize_source_and_target();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs an <c>overwrite_buffer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_Filter">
|
|
/// A filter function which determines whether offered messages should be accepted.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>overwrite_buffer</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
overwrite_buffer(filter_method const& _Filter) :
|
|
_M_fIsInitialized(false), _M_pMessage(NULL), _M_pReservedMessage(NULL)
|
|
{
|
|
initialize_source_and_target();
|
|
register_filter(_Filter);
|
|
}
|
|
|
|
#ifdef _CRT_USE_WINAPI_FAMILY_DESKTOP_APP
|
|
/// <summary>
|
|
/// Constructs an <c>overwrite_buffer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// The <c>Scheduler</c> object within which the propagation task for the <c>overwrite_buffer</c> messaging block is scheduled.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>overwrite_buffer</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
overwrite_buffer(Scheduler& _PScheduler) :
|
|
_M_fIsInitialized(false), _M_pMessage(NULL), _M_pReservedMessage(NULL)
|
|
{
|
|
initialize_source_and_target(&_PScheduler);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs an <c>overwrite_buffer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// The <c>Scheduler</c> object within which the propagation task for the <c>overwrite_buffer</c> messaging block is scheduled.
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A filter function which determines whether offered messages should be accepted.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>overwrite_buffer</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
overwrite_buffer(Scheduler& _PScheduler,
|
|
filter_method const& _Filter) :
|
|
_M_fIsInitialized(false), _M_pMessage(NULL), _M_pReservedMessage(NULL)
|
|
{
|
|
initialize_source_and_target(&_PScheduler);
|
|
register_filter(_Filter);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs an <c>overwrite_buffer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The <c>ScheduleGroup</c> object within which the propagation task for the <c>overwrite_buffer</c> messaging block is scheduled.
|
|
/// The <c>Scheduler</c> object used is implied by the schedule group.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>overwrite_buffer</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
overwrite_buffer(ScheduleGroup& _PScheduleGroup) :
|
|
_M_fIsInitialized(false), _M_pMessage(NULL), _M_pReservedMessage(NULL)
|
|
{
|
|
initialize_source_and_target(NULL, &_PScheduleGroup);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs an <c>overwrite_buffer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The <c>ScheduleGroup</c> object within which the propagation task for the <c>overwrite_buffer</c> messaging block is scheduled.
|
|
/// The <c>Scheduler</c> object used is implied by the schedule group.
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A filter function which determines whether offered messages should be accepted.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>overwrite_buffer</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
overwrite_buffer(ScheduleGroup& _PScheduleGroup,
|
|
filter_method const& _Filter) :
|
|
_M_fIsInitialized(false), _M_pMessage(NULL), _M_pReservedMessage(NULL)
|
|
{
|
|
initialize_source_and_target(NULL, &_PScheduleGroup);
|
|
register_filter(_Filter);
|
|
}
|
|
#endif /* _CRT_USE_WINAPI_FAMILY_DESKTOP_APP */
|
|
|
|
/// <summary>
|
|
/// Destroys the <c>overwrite_buffer</c> messaging block.
|
|
/// </summary>
|
|
/**/
|
|
~overwrite_buffer()
|
|
{
|
|
// Remove all links that are targets of this overwrite_buffer
|
|
remove_network_links();
|
|
|
|
// Clean up any messages left in this message block
|
|
_Delete_stored_messages();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks whether this <c>overwrite_buffer</c> messaging block has a value yet.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// <c>true</c> if the block has received a value, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/**/
|
|
bool has_value() const
|
|
{
|
|
return _M_fIsInitialized != 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a reference to the current payload of the message being stored in the <c>overwrite_buffer</c> messaging block.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The payload of the currently stored message.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// The value stored in the <c>overwrite_buffer</c> could change immediately after this method returns. This method will
|
|
/// wait until a message arrives if no message is currently stored in the <c>overwrite_buffer</c>.
|
|
/// </remarks>
|
|
/**/
|
|
_Type value()
|
|
{
|
|
return receive<_Type>(this);
|
|
}
|
|
|
|
protected:
|
|
|
|
//
|
|
// propagator_block protected function implementation
|
|
//
|
|
|
|
/// <summary>
|
|
/// Asynchronously passes a message from an <c>ISource</c> block to this <c>overwrite_buffer</c> messaging block.
|
|
/// It is invoked by the <c>propagate</c> method, when called by a source block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/**/
|
|
virtual message_status propagate_message(_Inout_ message<_Type> * _PMessage, _Inout_ ISource<_Type> * _PSource)
|
|
{
|
|
// It is important that calls to propagate do *not* take the same lock on the
|
|
// internal structure that is used by Consume and the LWT. Doing so could
|
|
// result in a deadlock with the Consume call.
|
|
|
|
message_status _Result = accepted;
|
|
|
|
_PMessage = _PSource->accept(_PMessage->msg_id(), this);
|
|
|
|
//
|
|
// If message was accepted, set the member variables for
|
|
// this block and start the asynchronous propagation task
|
|
//
|
|
if (_PMessage != NULL)
|
|
{
|
|
// Add a reference for the async_send holding the message
|
|
_PMessage->add_ref();
|
|
|
|
async_send(_PMessage);
|
|
}
|
|
else
|
|
{
|
|
_Result = missed;
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Synchronously passes a message from an <c>ISource</c> block to this <c>overwrite_buffer</c> messaging block.
|
|
/// It is invoked by the <c>send</c> method, when called by a source block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/**/
|
|
virtual message_status send_message(_Inout_ message<_Type> * _PMessage, _Inout_ ISource<_Type> * _PSource)
|
|
{
|
|
_PMessage = _PSource->accept(_PMessage->msg_id(), this);
|
|
|
|
//
|
|
// If message was accepted, set the member variables for
|
|
// this block and start the asynchronous propagation task
|
|
//
|
|
if (_PMessage != NULL)
|
|
{
|
|
// Add a reference for the sync_send holding the message
|
|
_PMessage->add_ref();
|
|
|
|
sync_send(_PMessage);
|
|
}
|
|
else
|
|
{
|
|
return missed;
|
|
}
|
|
|
|
return accepted;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Overrides the <c>supports_anonymous_source</c> method to indicate that this block can
|
|
/// accept messages offered to it by a source that is not linked.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// <c>true</c> because the block does not postpone offered messages.
|
|
/// </returns>
|
|
/**/
|
|
virtual bool supports_anonymous_source()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accepts a message that was offered by this <c>overwrite_buffer</c> messaging block,
|
|
/// returning a copy of the message to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the offered <c>message</c> object.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the <c>message</c> object that the caller now has ownership of.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// The <c>overwrite_buffer</c> messaging block returns copies of the message
|
|
/// to its targets, rather than transferring ownership of the currently
|
|
/// held message.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message<_Type> * accept_message(runtime_object_identity _MsgId)
|
|
{
|
|
//
|
|
// If the internal message has not yet been initialized yet, return NULL
|
|
//
|
|
if (_M_pMessage == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Instead of returning the internal message, we return a copy of the
|
|
// message stored.
|
|
//
|
|
// Because we are returning a copy, the accept routine for an overwritebuffer
|
|
// does not need to grab the internalLock
|
|
//
|
|
message<_Type> * _Msg = NULL;
|
|
if (_M_pMessage->msg_id() == _MsgId)
|
|
{
|
|
_Msg = new message<_Type>(_M_pMessage->payload);
|
|
}
|
|
|
|
return _Msg;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reserves a message previously offered by this <c>overwrite_buffer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being reserved.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the message was successfully reserved, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// After <c>reserve</c> is called, if it returns <c>true</c>, either <c>consume</c> or <c>release</c> must be called
|
|
/// to either take or release ownership of the message.
|
|
/// </remarks>
|
|
/**/
|
|
virtual bool reserve_message(runtime_object_identity _MsgId)
|
|
{
|
|
// Ensure that this message currently exists in the overwrite buffer
|
|
if (_M_pMessage == NULL || _M_pMessage->msg_id() != _MsgId)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Can only reserve one message, any other blocks trying to reserve
|
|
// will return false
|
|
_CONCRT_ASSERT(_M_pReservedMessage == NULL);
|
|
|
|
// Save this message away
|
|
_M_pReservedMessage = _M_pMessage;
|
|
|
|
// Add a reference for this message to prevent deletion
|
|
_M_pReservedMessage->add_ref();
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Consumes a message previously offered by the <c>overwrite_buffer</c> messaging block and reserved by the target,
|
|
/// returning a copy of the message to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being consumed.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the <c>message</c> object that the caller now has ownership of.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// Similar to <c>accept</c>, but is always preceded by a call to <c>reserve</c>.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message<_Type> * consume_message(runtime_object_identity _MsgId)
|
|
{
|
|
// Leave and return NULL if this msgId doesn't match the reserved message
|
|
// Otherwise this is a pull of a later overwritten message, and messages
|
|
// could them appear out of order.
|
|
if (_M_pReservedMessage != NULL && _M_pReservedMessage->msg_id() != _MsgId)
|
|
{
|
|
return NULL;
|
|
}
|
|
// This redundant assert is specifically to make the /analyze switch happy, which cannot recognize the same assertion above in if stmnt.
|
|
_CONCRT_ASSERT( _M_pReservedMessage != NULL );
|
|
|
|
_Type _Payload = _M_pReservedMessage->payload;
|
|
|
|
// Take the reserved message
|
|
message<_Type> * _Result = new message<_Type>(_Payload);
|
|
|
|
if (_M_pReservedMessage->remove_ref() == 0)
|
|
{
|
|
delete _M_pReservedMessage;
|
|
}
|
|
_M_pReservedMessage = NULL;
|
|
|
|
return _Result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases a previous message reservation.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being released.
|
|
/// </param>
|
|
/**/
|
|
virtual void release_message(runtime_object_identity _MsgId)
|
|
{
|
|
_CONCRT_ASSERT(_M_fIsInitialized);
|
|
_CONCRT_ASSERT(_M_pReservedMessage != NULL);
|
|
|
|
if (_MsgId != _M_pReservedMessage->msg_id())
|
|
{
|
|
throw message_not_found();
|
|
}
|
|
|
|
if (_M_pReservedMessage->remove_ref() == 0)
|
|
{
|
|
delete _M_pReservedMessage;
|
|
}
|
|
_M_pReservedMessage = NULL;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resumes propagation after a reservation has been released.
|
|
/// </summary>
|
|
/**/
|
|
virtual void resume_propagation()
|
|
{
|
|
// On reservation we do not stop propagation. So there
|
|
// is nothing to be done to resume propagation.
|
|
}
|
|
|
|
/// <summary>
|
|
/// A callback that notifies that a new target has been linked to this <c>overwrite_buffer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the newly linked target.
|
|
/// </param>
|
|
/**/
|
|
virtual void link_target_notification(_Inout_ ITarget<_Type> * _PTarget)
|
|
{
|
|
// If there is a message available already, propagate it
|
|
if (_M_pMessage != NULL)
|
|
{
|
|
_PTarget->propagate(_M_pMessage, this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Places the <c>message</c> <paramref name="_PMessage"/> in this <c>overwrite_buffer</c> messaging block and
|
|
/// offers it to all of the linked targets.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to a <c>message</c> object that this <c>overwrite_buffer</c> has taken ownership of.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This method overwrites the current message in the <c>overwrite_buffer</c> with the newly
|
|
/// accepted message <paramref name="_PMessage"/>.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void propagate_to_any_targets(_Inout_ message<_Type> * _PMessage)
|
|
{
|
|
// Move the message from the queuedMessages Buffer to the internal storage
|
|
|
|
// Add a reference for the overwrite_buffer holding the message
|
|
_PMessage->add_ref();
|
|
|
|
if (_M_pMessage != NULL)
|
|
{
|
|
if (_M_pMessage->remove_ref() == 0)
|
|
{
|
|
delete _M_pMessage;
|
|
}
|
|
}
|
|
|
|
_M_pMessage = _PMessage;
|
|
|
|
// Now that message has been received, set this block as initialized
|
|
_M_fIsInitialized = true;
|
|
|
|
for (target_iterator _Iter = _M_connectedTargets.begin(); *_Iter != NULL; ++_Iter)
|
|
{
|
|
// Overwrite buffers can propagate its message out
|
|
// to any number of Targets
|
|
|
|
ITarget<_Type> * _PTarget = *_Iter;
|
|
_PTarget->propagate(_PMessage, this);
|
|
}
|
|
|
|
if (_PMessage->remove_ref() == 0)
|
|
{
|
|
delete _PMessage;
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
/// <summary>
|
|
/// Deletes all messages currently stored in this message block. Should be called
|
|
/// by the destructor to ensure any messages propagated in are cleaned up.
|
|
/// </summary>
|
|
/**/
|
|
void _Delete_stored_messages()
|
|
{
|
|
// Input messages for this message block are in the base-class input buffer
|
|
// All messages in that buffer are guaranteed to have moved to the output
|
|
// buffer because the destructor first waits for all async sends to finish
|
|
// before reaching this point
|
|
|
|
// The messages for an overwrite buffer are deleted when overwritten
|
|
// through reference counting. This final check is put in place in
|
|
// case any message still exists in the buffer when the overwrite_buffer
|
|
// is deleted. The reference count of this message has not yet reached
|
|
// zero because it hasn't been overwritten yet. It is safe because of
|
|
// we have finished all propagation.
|
|
if (_M_pMessage != NULL)
|
|
{
|
|
// A block can only have a reserved message after receiving a message
|
|
// at some point, so it must be within the above if-clause.
|
|
// Now delete the reserved message if it is non-NULL and different from
|
|
// the saved internal message
|
|
if (_M_pReservedMessage != NULL && _M_pReservedMessage != _M_pMessage)
|
|
{
|
|
delete _M_pReservedMessage;
|
|
}
|
|
delete _M_pMessage;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Private Data Members
|
|
//
|
|
|
|
// The message being stored
|
|
message<_Type> * _M_pMessage;
|
|
|
|
// The message being reserved
|
|
message<_Type> * _M_pReservedMessage;
|
|
|
|
// The marker for whether the overwrite buffer has already been initialized
|
|
volatile bool _M_fIsInitialized;
|
|
|
|
private:
|
|
//
|
|
// Hide assignment operator and copy constructor
|
|
//
|
|
overwrite_buffer const &operator =(overwrite_buffer const&); // no assignment operator
|
|
overwrite_buffer(overwrite_buffer const &); // no copy constructor
|
|
};
|
|
|
|
//**************************************************************************
|
|
// Call:
|
|
//**************************************************************************
|
|
|
|
/// <summary>
|
|
/// A <c>call</c> messaging block is a multi-source, ordered <c>target_block</c> that
|
|
/// invokes a specified function when receiving a message.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload type of the messages propagated to this block.
|
|
/// </typeparam>
|
|
/// <typeparam name="_FunctorType">
|
|
/// The signature of functions that this block can accept.
|
|
/// </typeparam>
|
|
/// <remarks>
|
|
/// For more information, see <see cref="Asynchronous Message Blocks"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="transformer Class"/>
|
|
/**/
|
|
template<class _Type, class _FunctorType = std::tr1::function<void(_Type const&)>>
|
|
class call : public target_block<multi_link_registry<ISource<_Type>>>
|
|
{
|
|
/// <summary>
|
|
/// The function type that this block executes upon receiving a <c>message</c>.
|
|
/// </summary>
|
|
/**/
|
|
typedef _FunctorType _Call_method;
|
|
|
|
public:
|
|
/// <summary>
|
|
/// Constructs a <c>call</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_Func">
|
|
/// A function that will be invoked for each accepted message.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="_Call_method"/> is a functor with signature <c>void (_Type const &)</c>
|
|
/// which is invoked by this <c>call</c> messaging block to process a message.</para>
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>call</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
call(_Call_method const& _Func) :
|
|
_M_pFunc(_Func)
|
|
{
|
|
initialize_target();
|
|
enable_batched_processing();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>call</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_Filter">
|
|
/// A filter function which determines whether offered messages should be accepted.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="_Call_method"/> is a functor with signature <c>void (_Type const &)</c>
|
|
/// which is invoked by this <c>call</c> messaging block to process a message.</para>
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>call</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
call(_Call_method const& _Func,
|
|
filter_method const& _Filter) :
|
|
_M_pFunc(_Func)
|
|
{
|
|
initialize_target();
|
|
enable_batched_processing();
|
|
register_filter(_Filter);
|
|
}
|
|
|
|
#ifdef _CRT_USE_WINAPI_FAMILY_DESKTOP_APP
|
|
/// <summary>
|
|
/// Constructs a <c>call</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// The <c>Scheduler</c> object within which the propagation task for the <c>call</c> messaging block is scheduled.
|
|
/// </param>
|
|
/// <param name="_Func">
|
|
/// A function that will be invoked for each accepted message.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="_Call_method"/> is a functor with signature <c>void (_Type const &)</c>
|
|
/// which is invoked by this <c>call</c> messaging block to process a message.</para>
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>call</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
call(Scheduler& _PScheduler,
|
|
_Call_method const& _Func) :
|
|
_M_pFunc(_Func)
|
|
{
|
|
initialize_target(&_PScheduler);
|
|
enable_batched_processing();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>call</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// The <c>Scheduler</c> object within which the propagation task for the <c>call</c> messaging block is scheduled.
|
|
/// </param>
|
|
/// <param name="_Func">
|
|
/// A function that will be invoked for each accepted message.
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A filter function which determines whether offered messages should be accepted.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="_Call_method"/> is a functor with signature <c>void (_Type const &)</c>
|
|
/// which is invoked by this <c>call</c> messaging block to process a message.</para>
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>call</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
call(Scheduler& _PScheduler,
|
|
_Call_method const& _Func,
|
|
filter_method const& _Filter) :
|
|
_M_pFunc(_Func)
|
|
{
|
|
initialize_target(&_PScheduler);
|
|
enable_batched_processing();
|
|
register_filter(_Filter);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>call</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The <c>ScheduleGroup</c> object within which the propagation task for the <c>call</c> messaging block is scheduled.
|
|
/// The <c>Scheduler</c> object used is implied by the schedule group.
|
|
/// </param>
|
|
/// <param name="_Func">
|
|
/// A function that will be invoked for each accepted message.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="_Call_method"/> is a functor with signature <c>void (_Type const &)</c>
|
|
/// which is invoked by this <c>call</c> messaging block to process a message.</para>
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>call</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
call(ScheduleGroup& _PScheduleGroup,
|
|
_Call_method const& _Func) :
|
|
_M_pFunc(_Func)
|
|
{
|
|
initialize_target(NULL, &_PScheduleGroup);
|
|
enable_batched_processing();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>call</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The <c>ScheduleGroup</c> object within which the propagation task for the <c>call</c> messaging block is scheduled.
|
|
/// The <c>Scheduler</c> object used is implied by the schedule group.
|
|
/// </param>
|
|
/// <param name="_Func">
|
|
/// A function that will be invoked for each accepted message.
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A filter function which determines whether offered messages should be accepted.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="_Call_method"/> is a functor with signature <c>void (_Type const &)</c>
|
|
/// which is invoked by this <c>call</c> messaging block to process a message.</para>
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>call</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
call(ScheduleGroup& _PScheduleGroup,
|
|
_Call_method const& _Func,
|
|
filter_method const& _Filter) :
|
|
_M_pFunc(_Func)
|
|
{
|
|
initialize_target(NULL, &_PScheduleGroup);
|
|
enable_batched_processing();
|
|
register_filter(_Filter);
|
|
}
|
|
#endif /* _CRT_USE_WINAPI_FAMILY_DESKTOP_APP */
|
|
|
|
/// <summary>
|
|
/// Destroys the <c>call</c> messaging block.
|
|
/// </summary>
|
|
/**/
|
|
~call()
|
|
{
|
|
remove_sources();
|
|
}
|
|
|
|
protected:
|
|
|
|
//
|
|
// target_block protected function implementations
|
|
//
|
|
|
|
/// <summary>
|
|
/// Asynchronously passes a message from an <c>ISource</c> block to this <c>call</c> messaging block. It is invoked
|
|
/// by the <c>propagate</c> method, when called by a source block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/**/
|
|
virtual message_status propagate_message(_Inout_ message<_Type> * _PMessage, _Inout_ ISource<_Type> * _PSource)
|
|
{
|
|
// It is important that calls to propagate do *not* take the same lock on the
|
|
// internal structure that is used by Consume and the LWT. Doing so could
|
|
// result in a deadlock with the Consume call.
|
|
|
|
message_status _Result = accepted;
|
|
|
|
//
|
|
// Accept the message being propagated
|
|
// Note: depending on the source block propagating the message
|
|
// this may not necessarily be the same message (pMessage) first
|
|
// passed into the function.
|
|
//
|
|
_PMessage = _PSource->accept(_PMessage->msg_id(), this);
|
|
|
|
if (_PMessage != NULL)
|
|
{
|
|
async_send(_PMessage);
|
|
}
|
|
else
|
|
{
|
|
_Result = missed;
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Synchronously passes a message from an <c>ISource</c> block to this <c>call</c> messaging block. It is invoked
|
|
/// by the <c>send</c> method, when called by a source block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/**/
|
|
virtual message_status send_message(_Inout_ message<_Type> * _PMessage, _Inout_ ISource<_Type> * _PSource)
|
|
{
|
|
message_status _Result = accepted;
|
|
|
|
//
|
|
// Accept the message being propagated
|
|
// Note: depending on the source block propagating the message
|
|
// this may not necessarily be the same message (pMessage) first
|
|
// passed into the function.
|
|
//
|
|
_PMessage = _PSource->accept(_PMessage->msg_id(), this);
|
|
|
|
if (_PMessage != NULL)
|
|
{
|
|
sync_send(_PMessage);
|
|
}
|
|
else
|
|
{
|
|
_Result = missed;
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Overrides the <c>supports_anonymous_source</c> method to indicate that this block can
|
|
/// accept messages offered to it by a source that is not linked.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// <c>true</c> because the block does not postpone offered messages.
|
|
/// </returns>
|
|
/**/
|
|
virtual bool supports_anonymous_source()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes a message that was accepted by this <c>call</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the message that is to be handled.
|
|
/// </param>
|
|
/**/
|
|
virtual void process_message(_Inout_ message<_Type> * _PMessage)
|
|
{
|
|
// No longer necessary with CRT110 change
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes the call function on the input messages.
|
|
/// </summary>
|
|
/**/
|
|
virtual void process_input_messages(_Inout_ message<_Type> * _PMessage)
|
|
{
|
|
// Invoke the function provided by the user
|
|
_CONCRT_ASSERT(_PMessage != NULL);
|
|
_M_pFunc(_PMessage->payload);
|
|
delete _PMessage;
|
|
}
|
|
|
|
private:
|
|
|
|
//
|
|
// Private Data Members
|
|
//
|
|
|
|
// The call method called by this block
|
|
_Call_method _M_pFunc;
|
|
|
|
private:
|
|
//
|
|
// Hide assignment operator and copy constructor
|
|
//
|
|
call const &operator =(call const&); // no assignment operator
|
|
call(call const &); // no copy constructor
|
|
};
|
|
|
|
//**************************************************************************
|
|
// Transformer:
|
|
//**************************************************************************
|
|
|
|
/// <summary>
|
|
/// A <c>transformer</c> messaging block is a single-target, multi-source, ordered
|
|
/// <c>propagator_block</c> which can accept messages of one type and is
|
|
/// capable of storing an unbounded number of messages of a different type.
|
|
/// </summary>
|
|
/// <typeparam name="_Input">
|
|
/// The payload type of the messages accepted by the buffer.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Output">
|
|
/// The payload type of the messages stored and propagated out by the buffer.
|
|
/// </typeparam>
|
|
/// <remarks>
|
|
/// For more information, see <see cref="Asynchronous Message Blocks"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="call Class"/>
|
|
/**/
|
|
template<class _Input, class _Output>
|
|
class transformer : public propagator_block<single_link_registry<ITarget<_Output>>, multi_link_registry<ISource<_Input>>>
|
|
{
|
|
typedef std::tr1::function<_Output(_Input const&)> _Transform_method;
|
|
|
|
public:
|
|
/// <summary>
|
|
/// Constructs a <c>transformer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_Func">
|
|
/// A function that will be invoked for each accepted message.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to a target block to link with the transformer.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="_Transform_method"/> is a functor with signature <c>_Output (_Input const &)</c>
|
|
/// which is invoked by this <c>transformer</c> messaging block to process a message.</para>
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Input const &)</c>
|
|
/// which is invoked by this <c>transformer</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
transformer(_Transform_method const& _Func,
|
|
_Inout_opt_ ITarget<_Output> * _PTarget = NULL) :
|
|
_M_pFunc(_Func)
|
|
{
|
|
initialize_source_and_target();
|
|
|
|
if (_PTarget != NULL)
|
|
{
|
|
link_target(_PTarget);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>transformer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_Func">
|
|
/// A function that will be invoked for each accepted message.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to a target block to link with the transform.
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A filter function which determines whether offered messages should be accepted.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="_Transform_method"/> is a functor with signature <c>_Output (_Input const &)</c>
|
|
/// which is invoked by this <c>transformer</c> messaging block to process a message.</para>
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Input const &)</c>
|
|
/// which is invoked by this <c>transformer</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
transformer(_Transform_method const& _Func,
|
|
_Inout_opt_ ITarget<_Output> * _PTarget,
|
|
filter_method const& _Filter) :
|
|
_M_pFunc(_Func)
|
|
{
|
|
initialize_source_and_target();
|
|
register_filter(_Filter);
|
|
|
|
if (_PTarget != NULL)
|
|
{
|
|
link_target(_PTarget);
|
|
}
|
|
}
|
|
|
|
#ifdef _CRT_USE_WINAPI_FAMILY_DESKTOP_APP
|
|
/// <summary>
|
|
/// Constructs a <c>transformer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// The <c>Scheduler</c> object within which the propagation task for the <c>transformer</c> messaging block is scheduled.
|
|
/// </param>
|
|
/// <param name="_Func">
|
|
/// A function that will be invoked for each accepted message.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to a target block to link with the transformer.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="_Transform_method"/> is a functor with signature <c>_Output (_Input const &)</c>
|
|
/// which is invoked by this <c>transformer</c> messaging block to process a message.</para>
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Input const &)</c>
|
|
/// which is invoked by this <c>transformer</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
transformer(Scheduler& _PScheduler,
|
|
_Transform_method const& _Func,
|
|
_Inout_opt_ ITarget<_Output> * _PTarget = NULL) :
|
|
_M_pFunc(_Func)
|
|
{
|
|
initialize_source_and_target(&_PScheduler);
|
|
|
|
if (_PTarget != NULL)
|
|
{
|
|
link_target(_PTarget);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>transformer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// The <c>Scheduler</c> object within which the propagation task for the <c>transformer</c> messaging block is scheduled.
|
|
/// </param>
|
|
/// <param name="_Func">
|
|
/// A function that will be invoked for each accepted message.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to a target block to link with the transformer.
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A filter function which determines whether offered messages should be accepted.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="_Transform_method"/> is a functor with signature <c>_Output (_Input const &)</c>
|
|
/// which is invoked by this <c>transformer</c> messaging block to process a message.</para>
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Input const &)</c>
|
|
/// which is invoked by this <c>transformer</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
transformer(Scheduler& _PScheduler,
|
|
_Transform_method const& _Func,
|
|
_Inout_opt_ ITarget<_Output> * _PTarget,
|
|
filter_method const& _Filter) :
|
|
_M_pFunc(_Func)
|
|
{
|
|
initialize_source_and_target(&_PScheduler);
|
|
register_filter(_Filter);
|
|
|
|
if (_PTarget != NULL)
|
|
{
|
|
link_target(_PTarget);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>transformer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The <c>ScheduleGroup</c> object within which the propagation task for the <c>transformer</c> messaging block is scheduled.
|
|
/// The <c>Scheduler</c> object used is implied by the schedule group.
|
|
/// </param>
|
|
/// <param name="_Func">
|
|
/// A function that will be invoked for each accepted message.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to a target block to link with the transformer.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="_Transform_method"/> is a functor with signature <c>_Output (_Input const &)</c>
|
|
/// which is invoked by this <c>transformer</c> messaging block to process a message.</para>
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Input const &)</c>
|
|
/// which is invoked by this <c>transformer</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
transformer(ScheduleGroup& _PScheduleGroup,
|
|
_Transform_method const& _Func,
|
|
_Inout_opt_ ITarget<_Output> * _PTarget = NULL) :
|
|
_M_pFunc(_Func)
|
|
{
|
|
initialize_source_and_target(NULL, &_PScheduleGroup);
|
|
|
|
if (_PTarget != NULL)
|
|
{
|
|
link_target(_PTarget);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>transformer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The <c>ScheduleGroup</c> object within which the propagation task for the <c>transformer</c> messaging block is scheduled.
|
|
/// The <c>Scheduler</c> object used is implied by the schedule group.
|
|
/// </param>
|
|
/// <param name="_Func">
|
|
/// A function that will be invoked for each accepted message.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to a target block to link with the transformer.
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A filter function which determines whether offered messages should be accepted.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="_Transform_method"/> is a functor with signature <c>_Output (_Input const &)</c>
|
|
/// which is invoked by this <c>transformer</c> messaging block to process a message.</para>
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Input const &)</c>
|
|
/// which is invoked by this <c>transformer</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
transformer(ScheduleGroup& _PScheduleGroup,
|
|
_Transform_method const& _Func,
|
|
_Inout_opt_ ITarget<_Output> * _PTarget,
|
|
filter_method const& _Filter) :
|
|
_M_pFunc(_Func)
|
|
{
|
|
initialize_source_and_target(NULL, &_PScheduleGroup);
|
|
register_filter(_Filter);
|
|
|
|
if (_PTarget != NULL)
|
|
{
|
|
link_target(_PTarget);
|
|
}
|
|
}
|
|
#endif /* _CRT_USE_WINAPI_FAMILY_DESKTOP_APP */
|
|
|
|
/// <summary>
|
|
/// Destroys the <c>transformer</c> messaging block.
|
|
/// </summary>
|
|
/**/
|
|
~transformer()
|
|
{
|
|
// Remove all links
|
|
remove_network_links();
|
|
|
|
// Clean up any messages left in this message block
|
|
_Delete_stored_messages();
|
|
}
|
|
|
|
protected:
|
|
|
|
// Propagator_block protected function implementations
|
|
|
|
/// <summary>
|
|
/// Asynchronously passes a message from an <c>ISource</c> block to this <c>transformer</c> messaging block.
|
|
/// It is invoked by the <c>propagate</c> method, when called by a source block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/**/
|
|
virtual message_status propagate_message(_Inout_ message<_Input> * _PMessage, _Inout_ ISource<_Input> * _PSource)
|
|
{
|
|
// It is important that calls to propagate do *not* take the same lock on the
|
|
// internal structure that is used by Consume and the LWT. Doing so could
|
|
// result in a deadlock with the Consume call.
|
|
|
|
message_status _Result = accepted;
|
|
|
|
//
|
|
// Accept the message being propagated
|
|
// Note: depending on the source block propagating the message
|
|
// this may not necessarily be the same message (pMessage) first
|
|
// passed into the function.
|
|
//
|
|
_PMessage = _PSource->accept(_PMessage->msg_id(), this);
|
|
|
|
if (_PMessage != NULL)
|
|
{
|
|
// Enqueue the input message
|
|
_M_inputMessages.push(_PMessage);
|
|
async_send(NULL);
|
|
}
|
|
else
|
|
{
|
|
_Result = missed;
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Synchronously passes a message from an <c>ISource</c> block to this <c>transformer</c> messaging block.
|
|
/// It is invoked by the <c>send</c> method, when called by a source block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/**/
|
|
virtual message_status send_message(_Inout_ message<_Input> * _PMessage, _Inout_ ISource<_Input> * _PSource)
|
|
{
|
|
_PMessage = _PSource->accept(_PMessage->msg_id(), this);
|
|
|
|
if (_PMessage != NULL)
|
|
{
|
|
// Enqueue the input message
|
|
_M_inputMessages.push(_PMessage);
|
|
sync_send(NULL);
|
|
}
|
|
else
|
|
{
|
|
return missed;
|
|
}
|
|
|
|
return accepted;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Overrides the <c>supports_anonymous_source</c> method to indicate that this block can
|
|
/// accept messages offered to it by a source that is not linked.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// <c>true</c> because the block does not postpone offered messages.
|
|
/// </returns>
|
|
/**/
|
|
virtual bool supports_anonymous_source()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accepts a message that was offered by this <c>transformer</c> messaging block,
|
|
/// transferring ownership to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the offered <c>message</c> object.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the <c>message</c> object that the caller now has ownership of.
|
|
/// </returns>
|
|
/**/
|
|
virtual message<_Output> * accept_message(runtime_object_identity _MsgId)
|
|
{
|
|
//
|
|
// Peek at the head message in the message buffer. If the IDs match
|
|
// dequeue and transfer ownership
|
|
//
|
|
message<_Output> * _Msg = NULL;
|
|
|
|
if (_M_messageBuffer._Is_head(_MsgId))
|
|
{
|
|
_Msg = _M_messageBuffer._Dequeue();
|
|
}
|
|
|
|
return _Msg;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reserves a message previously offered by this <c>transformer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being reserved.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the message was successfully reserved, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// After <c>reserve</c> is called, if it returns <c>true</c>, either <c>consume</c> or <c>release</c> must be called
|
|
/// to either take or release ownership of the message.
|
|
/// </remarks>
|
|
/**/
|
|
virtual bool reserve_message(runtime_object_identity _MsgId)
|
|
{
|
|
// Allow reservation if this is the head message
|
|
return _M_messageBuffer._Is_head(_MsgId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Consumes a message previously offered by the <c>transformer</c> and reserved by the target,
|
|
/// transferring ownership to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being consumed.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the <c>message</c> object that the caller now has ownership of.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// Similar to <c>accept</c>, but is always preceded by a call to <c>reserve</c>.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message<_Output> * consume_message(runtime_object_identity _MsgId)
|
|
{
|
|
// By default, accept the message
|
|
return accept_message(_MsgId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases a previous message reservation.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being released.
|
|
/// </param>
|
|
/**/
|
|
virtual void release_message(runtime_object_identity _MsgId)
|
|
{
|
|
// The head message is the one reserved.
|
|
if (!_M_messageBuffer._Is_head(_MsgId))
|
|
{
|
|
throw message_not_found();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resumes propagation after a reservation has been released.
|
|
/// </summary>
|
|
/**/
|
|
virtual void resume_propagation()
|
|
{
|
|
// If there are any messages in the buffer, propagate them out
|
|
if (_M_messageBuffer._Count() > 0)
|
|
{
|
|
// async send a NULL value to initiate the repropagation
|
|
async_send(NULL);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A callback that notifies that a new target has been linked to this <c>transformer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the newly linked target.
|
|
/// </param>
|
|
/**/
|
|
virtual void link_target_notification(_Inout_ ITarget<_Output> *)
|
|
{
|
|
// If the message queue is blocked due to reservation
|
|
// there is no need to do any message propagation
|
|
if (_M_pReservedFor != NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_Propagate_priority_order(_M_messageBuffer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes the transformer function on the input messages.
|
|
/// </summary>
|
|
/**/
|
|
virtual void propagate_to_any_targets(_Inout_opt_ message<_Output> *)
|
|
{
|
|
message<_Output> * _Msg = NULL;
|
|
|
|
// Process input message.
|
|
message<_Input> * _PInputMessage = NULL;
|
|
_M_inputMessages.try_pop(_PInputMessage);
|
|
|
|
if (_PInputMessage != NULL)
|
|
{
|
|
// Invoke the TransformMethod on the data
|
|
// Let exceptions flow
|
|
_Output _Out = _M_pFunc(_PInputMessage->payload);
|
|
|
|
// Reuse the input message ID
|
|
_Msg = new message<_Output>(_Out, _PInputMessage->msg_id());
|
|
_M_messageBuffer._Enqueue(_Msg);
|
|
|
|
// Message cleanup
|
|
delete _PInputMessage;
|
|
|
|
if (!_M_messageBuffer._Is_head(_Msg->msg_id()))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
_Propagate_priority_order(_M_messageBuffer);
|
|
}
|
|
|
|
private:
|
|
|
|
/// <summary>
|
|
/// Propagates messages in priority order.
|
|
/// </summary>
|
|
/// <param name="_MessageBuffer">
|
|
/// Reference to a message queue with messages to be propagated
|
|
/// </param>
|
|
/**/
|
|
void _Propagate_priority_order(::Concurrency::details::_Queue<message<_Target_type>> & _MessageBuffer)
|
|
{
|
|
message<_Target_type> * _Msg = _MessageBuffer._Peek();
|
|
|
|
// If someone has reserved the _Head message, don't propagate anymore
|
|
if (_M_pReservedFor != NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
while (_Msg != NULL)
|
|
{
|
|
message_status _Status = declined;
|
|
|
|
// Always start from the first target that linked
|
|
for (target_iterator _Iter = _M_connectedTargets.begin(); *_Iter != NULL; ++_Iter)
|
|
{
|
|
ITarget<_Target_type> * _PTarget = *_Iter;
|
|
_Status = _PTarget->propagate(_Msg, this);
|
|
|
|
// Ownership of message changed. Do not propagate this
|
|
// message to any other target.
|
|
if (_Status == accepted)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// If the target just propagated to reserved this message, stop
|
|
// propagating it to others
|
|
if (_M_pReservedFor != NULL)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If status is anything other than accepted, then the head message
|
|
// was not propagated out. Thus, nothing after it in the queue can
|
|
// be propagated out. Cease propagation.
|
|
if (_Status != accepted)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Get the next message
|
|
_Msg = _MessageBuffer._Peek();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes all messages currently stored in this message block. Should be called
|
|
/// by the destructor to ensure any messages propagated in are cleaned up.
|
|
/// </summary>
|
|
/**/
|
|
void _Delete_stored_messages()
|
|
{
|
|
// Delete input messages
|
|
// Because the transformer uses its own input queue, it's possible there are messages
|
|
// in this queue and no LWT will be executed to handle them.
|
|
message<_Input> * _PInputQueueMessage = NULL;
|
|
|
|
while (_M_inputMessages.try_pop(_PInputQueueMessage))
|
|
{
|
|
// Message cleanup
|
|
delete _PInputQueueMessage;
|
|
}
|
|
|
|
// Delete any messages remaining in the output queue
|
|
for (;;)
|
|
{
|
|
message<_Output> * _Msg = _M_messageBuffer._Dequeue();
|
|
if (_Msg == NULL)
|
|
{
|
|
break;
|
|
}
|
|
delete _Msg;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Private Data Members
|
|
//
|
|
|
|
// The transformer method called by this block
|
|
_Transform_method _M_pFunc;
|
|
|
|
// The queue of input messages for this Transformer block
|
|
concurrent_queue<message<_Input> *> _M_inputMessages;
|
|
|
|
/// <summary>
|
|
/// Message queue used to store outbound messages
|
|
/// </summary>
|
|
/**/
|
|
::Concurrency::details::_Queue<message<_Output>> _M_messageBuffer;
|
|
|
|
private:
|
|
//
|
|
// Hide assignment operator and copy constructor
|
|
//
|
|
transformer const &operator =(transformer const &); // no assignment operator
|
|
transformer(transformer const &); // no copy constructor
|
|
};
|
|
|
|
//**************************************************************************
|
|
// Timer:
|
|
//**************************************************************************
|
|
/// <summary>
|
|
/// A <c>timer</c> messaging block is a single-target <c>source_block</c> capable of sending
|
|
/// a message to its target after a specified time period has elapsed
|
|
/// or at specific intervals.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload type of the output messages of this block.
|
|
/// </typeparam>
|
|
/// <remarks>
|
|
/// For more information, see <see cref="Asynchronous Message Blocks"/>.
|
|
/// </remarks>
|
|
/**/
|
|
template<class _Type>
|
|
class timer : public Concurrency::details::_Timer, public source_block<single_link_registry<ITarget<_Type>>>
|
|
{
|
|
private:
|
|
|
|
/// <summary>
|
|
/// Tracks the state machine of the timer.
|
|
/// </summary>
|
|
/**/
|
|
enum State
|
|
{
|
|
/// <summary>
|
|
/// The timer has been initialized, but not yet started.
|
|
/// </summary>
|
|
/**/
|
|
Initialized,
|
|
/// <summary>
|
|
/// The timer has been started.
|
|
/// </summary>
|
|
/**/
|
|
Started,
|
|
/// <summary>
|
|
/// The timer has started and been paused.
|
|
/// </summary>
|
|
/**/
|
|
Paused,
|
|
/// <summary>
|
|
/// The timer has been stopped.
|
|
/// </summary>
|
|
/**/
|
|
Stopped
|
|
};
|
|
|
|
public:
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>timer</c> messaging block that will fire a given message after a specified interval.
|
|
/// </summary>
|
|
/// <param name="_Ms">
|
|
/// The number of milliseconds that must elapse after the call to start for the specified message
|
|
/// to be propagated downstream.
|
|
/// </param>
|
|
/// <param name="_Value">
|
|
/// The value which will be propagated downstream when the timer elapses.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the timer will propagate its message.
|
|
/// </param>
|
|
/// <param name="_Repeating">
|
|
/// If true, indicates that the timer will fire periodically every <paramref name="_Ms"/> milliseconds.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_Scheduler"/>
|
|
/// or <paramref name="_ScheduleGroup"/> parameters.
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
timer(unsigned int _Ms, _Type const& _Value, ITarget<_Type> *_PTarget = NULL, bool _Repeating = false) :
|
|
_Timer(_Ms, _Repeating)
|
|
{
|
|
_Initialize(_Value, _PTarget, _Repeating);
|
|
}
|
|
|
|
#ifdef _CRT_USE_WINAPI_FAMILY_DESKTOP_APP
|
|
/// <summary>
|
|
/// Constructs a <c>timer</c> messaging block that will fire a given message after a specified interval.
|
|
/// </summary>
|
|
/// <param name="_Scheduler">
|
|
/// The <c>Scheduler</c> object within which the propagation task for the <c>timer</c> messaging block is scheduled is scheduled.
|
|
/// </param>
|
|
/// <param name="_Ms">
|
|
/// The number of milliseconds that must elapse after the call to start for the specified message
|
|
/// to be propagated downstream.
|
|
/// </param>
|
|
/// <param name="_Value">
|
|
/// The value which will be propagated downstream when the timer elapses.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the timer will propagate its message.
|
|
/// </param>
|
|
/// <param name="_Repeating">
|
|
/// If true, indicates that the timer will fire periodically every <paramref name="_Ms"/> milliseconds.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_Scheduler"/>
|
|
/// or <paramref name="_ScheduleGroup"/> parameters.
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
timer(Scheduler& _Scheduler, unsigned int _Ms, _Type const& _Value, _Inout_opt_ ITarget<_Type> *_PTarget = NULL, bool _Repeating = false) :
|
|
_Timer(_Ms, _Repeating)
|
|
{
|
|
_Initialize(_Value, _PTarget, _Repeating, &_Scheduler);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>timer</c> messaging block that will fire a given message after a specified interval.
|
|
/// </summary>
|
|
/// <param name="_ScheduleGroup">
|
|
/// The <c>ScheduleGroup</c> object within which the propagation task for the <c>timer</c> messaging block is scheduled.
|
|
/// The <c>Scheduler</c> object used is implied by the schedule group.
|
|
/// </param>
|
|
/// <param name="_Ms">
|
|
/// The number of milliseconds that must elapse after the call to start for the specified message
|
|
/// to be propagated downstream.
|
|
/// </param>
|
|
/// <param name="_Value">
|
|
/// The value which will be propagated downstream when the timer elapses.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the timer will propagate its message.
|
|
/// </param>
|
|
/// <param name="_Repeating">
|
|
/// If true, indicates that the timer will fire periodically every <paramref name="_Ms"/> milliseconds.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_Scheduler"/>
|
|
/// or <paramref name="_ScheduleGroup"/> parameters.
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
timer(ScheduleGroup& _ScheduleGroup, unsigned int _Ms, _Type const& _Value, _Inout_opt_ ITarget<_Type> *_PTarget = NULL, bool _Repeating = false) :
|
|
_Timer(_Ms, _Repeating)
|
|
{
|
|
_Initialize(_Value, _PTarget, _Repeating, NULL, &_ScheduleGroup);
|
|
}
|
|
#endif /* _CRT_USE_WINAPI_FAMILY_DESKTOP_APP */
|
|
|
|
/// <summary>
|
|
/// Destroys a <c>timer</c> messaging block.
|
|
/// </summary>
|
|
/**/
|
|
~timer()
|
|
{
|
|
//
|
|
// Make sure there are no more outstanding timer fires. Note that this does not mean that the LWT that was queued is finished, it only
|
|
// means that no more timers will fire after the return from _Stop. We still *MUST* wait on any outstanding LWTs.
|
|
//
|
|
if (_M_state == Started)
|
|
_Stop();
|
|
|
|
// Remove all the targets. This will wait for any outstanding LWTs
|
|
remove_targets();
|
|
|
|
//
|
|
// No more asynchronous operations can happen as of this point.
|
|
//
|
|
|
|
// Clean up any messages left in this message block
|
|
_Delete_stored_messages();
|
|
|
|
if (_M_fReferencedScheduler)
|
|
{
|
|
::Concurrency::details::_Scheduler(_M_pScheduler)._Release();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the <c>timer</c> messaging block. The specified number of milliseconds after this is called, the specified value will be propagated
|
|
/// downstream as a <c>message</c>.
|
|
/// </summary>
|
|
/**/
|
|
void start()
|
|
{
|
|
if (_M_state == Initialized || _M_state == Paused)
|
|
{
|
|
_M_state = Started;
|
|
_Start();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops the <c>timer</c> messaging block.
|
|
/// </summary>
|
|
/**/
|
|
void stop()
|
|
{
|
|
if (_M_state == Started)
|
|
_Stop();
|
|
|
|
_M_state = Stopped;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops the <c>timer</c> messaging block. If it is a repeating <c>timer</c> messaging block, it can be restarted with a subsequent
|
|
/// <c>start()</c> call. For non-repeating timers, this has the same effect as a <c>stop</c> call.
|
|
/// </summary>
|
|
/**/
|
|
void pause()
|
|
{
|
|
//
|
|
// Non repeating timers cannot pause. They go to a final stopped state on pause.
|
|
//
|
|
if (!_M_fRepeating)
|
|
{
|
|
stop();
|
|
}
|
|
else
|
|
{
|
|
// Pause only a started timer.
|
|
|
|
if (_M_state == Started)
|
|
{
|
|
_Stop();
|
|
_M_state = Paused;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected:
|
|
|
|
/// <summary>
|
|
/// Accepts a message that was offered by this <c>timer</c> messaging block,
|
|
/// transferring ownership to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the offered <c>message</c> object.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the <c>message</c> object that the caller now has ownership of.
|
|
/// </returns>
|
|
/**/
|
|
virtual message<_Type> * accept_message(runtime_object_identity _MsgId)
|
|
{
|
|
if (_M_pMessage == NULL || _MsgId != _M_pMessage->msg_id())
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
message<_Type> *_PMessage = _M_pMessage;
|
|
_M_pMessage = NULL;
|
|
|
|
return _PMessage;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reserves a message previously offered by this <c>timer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being reserved.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the message was successfully reserved, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// After <c>reserve</c> is called, if it returns <c>true</c>, either <c>consume</c> or <c>release</c> must be called
|
|
/// to either take or release ownership of the message.
|
|
/// </remarks>
|
|
/**/
|
|
virtual bool reserve_message(runtime_object_identity _MsgId)
|
|
{
|
|
//
|
|
// Semantically, every timer tick is the same value -- it doesn't matter the message ID. Because we can only
|
|
// have one target as well, we do not need to track anything here.
|
|
//
|
|
if (_M_pMessage == NULL || _M_pMessage->msg_id() != _MsgId)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Consumes a message previously offered by the <c>timer</c> and reserved by the target,
|
|
/// transferring ownership to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being consumed.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the <c>message</c> object that the caller now has ownership of.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// Similar to <c>accept</c>, but is always preceded by a call to <c>reserve</c>.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message<_Type> * consume_message(runtime_object_identity _MsgId)
|
|
{
|
|
return accept_message(_MsgId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases a previous message reservation.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being released.
|
|
/// </param>
|
|
/**/
|
|
virtual void release_message(runtime_object_identity _MsgId)
|
|
{
|
|
if (_M_pMessage == NULL || _M_pMessage->msg_id() != _MsgId)
|
|
{
|
|
throw message_not_found();
|
|
}
|
|
|
|
delete _M_pMessage;
|
|
_M_pMessage = NULL;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resumes propagation after a reservation has been released.
|
|
/// </summary>
|
|
/**/
|
|
virtual void resume_propagation()
|
|
{
|
|
// Because reservation doesn't prevent propagation there is
|
|
// no need to resume on consume/release.
|
|
}
|
|
|
|
/// <summary>
|
|
/// A callback that notifies that a new target has been linked to this <c>timer</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the newly linked target.
|
|
/// </param>
|
|
/**/
|
|
virtual void link_target_notification(_Inout_ ITarget<_Type> * _PTarget)
|
|
{
|
|
// If there is a timer message sitting around, it must be propagated to the target now.
|
|
|
|
if (_M_pMessage != NULL)
|
|
{
|
|
_PTarget->propagate(_M_pMessage, this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to offer the message produced by the <c>timer</c> block to all of the linked targets.
|
|
/// </summary>
|
|
/**/
|
|
virtual void propagate_to_any_targets(_Inout_opt_ message<_Type> *)
|
|
{
|
|
if (_M_pMessage == NULL)
|
|
{
|
|
_M_pMessage = _NewMessage();
|
|
for (target_iterator _Iter = _M_connectedTargets.begin(); *_Iter != NULL; ++_Iter)
|
|
{
|
|
ITarget<_Type> * _PTarget = *_Iter;
|
|
_PTarget->propagate(_M_pMessage, this);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
// The timer message we contain
|
|
message<_Type> *_M_pMessage;
|
|
|
|
// Current state of the timer.
|
|
State _M_state;
|
|
|
|
// The value to send on elapse of the timer.
|
|
_Type _M_value;
|
|
|
|
// An indication of whether the timer is repeating.
|
|
bool _M_fRepeating;
|
|
|
|
// A flag for whether we need to release a reference on the scheduler.
|
|
bool _M_fReferencedScheduler;
|
|
|
|
// Scheduler used for the timer
|
|
Scheduler * _M_pScheduler;
|
|
|
|
/// <summary>
|
|
/// Allocates a new message.
|
|
/// </summary>
|
|
/**/
|
|
message<_Type>* _NewMessage() const
|
|
{
|
|
return new message<_Type>(_M_value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the timer fires.
|
|
/// </summary>
|
|
/**/
|
|
virtual void _Fire()
|
|
{
|
|
async_send(NULL);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Common initialization.
|
|
/// </summary>
|
|
/// <param name="_Value">
|
|
/// The value which will be propagated downstream when the timer elapses.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the timer will propagate its message.
|
|
/// </param>
|
|
/// <param name="_Repeating">
|
|
/// If true, indicates that the timer will fire periodically every _Ms milliseconds.
|
|
/// </param>
|
|
/**/
|
|
void _Initialize(const _Type& _Value, _Inout_ ITarget<_Type> *_PTarget, bool _Repeating, _Inout_opt_ Scheduler * _PScheduler = NULL, _Inout_opt_ ScheduleGroup * _PScheduleGroup = NULL)
|
|
{
|
|
_M_pMessage = NULL;
|
|
_M_value = _Value;
|
|
_M_fRepeating = _Repeating;
|
|
_M_state = Initialized;
|
|
_M_fReferencedScheduler = false;
|
|
|
|
//
|
|
// If we are going to utilize the current scheduler for timer firing, we need to capture it now. Otherwise,
|
|
// the timer threads fired from Windows (what _Fire executes within) will wind up with a default scheduler
|
|
// attached -- probably not the semantic we want.
|
|
//
|
|
if (_PScheduleGroup == NULL && _PScheduler == NULL)
|
|
{
|
|
::Concurrency::details::_Scheduler _sched = ::Concurrency::details::_CurrentScheduler::_Get();
|
|
_PScheduler = _sched._GetScheduler();
|
|
_sched._Reference();
|
|
_M_fReferencedScheduler = true;
|
|
}
|
|
|
|
_M_pScheduler = _PScheduler;
|
|
initialize_source(_PScheduler, _PScheduleGroup);
|
|
|
|
if (_PTarget != NULL)
|
|
{
|
|
link_target(_PTarget);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes all messages currently stored in this message block. Should be called
|
|
/// by the destructor to ensure any messages propagated in are cleaned up.
|
|
/// </summary>
|
|
/**/
|
|
void _Delete_stored_messages()
|
|
{
|
|
// Input messages for this message block are in the base-class input buffer
|
|
// All messages in that buffer are guaranteed to have moved to the output
|
|
// buffer because the destructor first waits for all async sends to finish
|
|
// before reaching this point
|
|
|
|
// Delete the message remaining in the output queue
|
|
if (_M_pMessage != NULL)
|
|
{
|
|
delete _M_pMessage;
|
|
}
|
|
}
|
|
|
|
private:
|
|
//
|
|
// Hide assignment operator and copy constructor
|
|
//
|
|
timer const &operator =(timer const &); // no assignment operator
|
|
timer(timer const &); // no copy constructor
|
|
};
|
|
|
|
//**************************************************************************
|
|
// Single assignment:
|
|
//**************************************************************************
|
|
|
|
/// <summary>
|
|
/// A <c>single_assignment</c> messaging block is a multi-target, multi-source, ordered
|
|
/// <c>propagator_block</c> capable of storing a single, write-once
|
|
/// <c>message</c>.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload type of the message stored and propagated by the buffer.
|
|
/// </typeparam>
|
|
/// <remarks>
|
|
/// A <c>single_assignment</c> messaging block propagates out copies of its message to each target.
|
|
/// <para>For more information, see <see cref="Asynchronous Message Blocks"/>.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="overwrite_buffer Class"/>
|
|
/// <seealso cref="unbounded_buffer Class"/>
|
|
/**/
|
|
template<class _Type>
|
|
class single_assignment : public propagator_block<multi_link_registry<ITarget<_Type>>, multi_link_registry<ISource<_Type>>>
|
|
{
|
|
public:
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>single_assignment</c> messaging block.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>single_assignment</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
single_assignment() :
|
|
_M_fIsInitialized(false), _M_pMessage(NULL)
|
|
{
|
|
initialize_source_and_target();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>single_assignment</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_Filter">
|
|
/// A filter function which determines whether offered messages should be accepted.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>single_assignment</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
single_assignment(filter_method const& _Filter) :
|
|
_M_fIsInitialized(false), _M_pMessage(NULL)
|
|
{
|
|
initialize_source_and_target();
|
|
register_filter(_Filter);
|
|
}
|
|
|
|
#ifdef _CRT_USE_WINAPI_FAMILY_DESKTOP_APP
|
|
/// <summary>
|
|
/// Constructs a <c>single_assignment</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// The <c>Scheduler</c> object within which the propagation task for the <c>single_assignment</c> messaging block is scheduled.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>single_assignment</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
single_assignment(Scheduler& _PScheduler) :
|
|
_M_fIsInitialized(false), _M_pMessage(NULL)
|
|
{
|
|
initialize_source_and_target(&_PScheduler);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>single_assignment</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// The <c>Scheduler</c> object within which the propagation task for the <c>single_assignment</c> messaging block is scheduled.
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A filter function which determines whether offered messages should be accepted.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>single_assignment</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
single_assignment(Scheduler& _PScheduler, filter_method const& _Filter) :
|
|
_M_fIsInitialized(false), _M_pMessage(NULL)
|
|
{
|
|
initialize_source_and_target(&_PScheduler);
|
|
register_filter(_Filter);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>single_assignment</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The <c>ScheduleGroup</c> object within which the propagation task for the <c>single_assignment</c> messaging block is scheduled.
|
|
/// The <c>Scheduler</c> object used is implied by the schedule group.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>single_assignment</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
single_assignment(ScheduleGroup& _PScheduleGroup) :
|
|
_M_fIsInitialized(false), _M_pMessage(NULL)
|
|
{
|
|
initialize_source_and_target(NULL, &_PScheduleGroup);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>single_assignment</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The <c>ScheduleGroup</c> object within which the propagation task for the <c>single_assignment</c> messaging block is scheduled.
|
|
/// The <c>Scheduler</c> object used is implied by the schedule group.
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A filter function which determines whether offered messages should be accepted.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>single_assignment</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
single_assignment(ScheduleGroup& _PScheduleGroup, filter_method const& _Filter) :
|
|
_M_fIsInitialized(false), _M_pMessage(NULL)
|
|
{
|
|
initialize_source_and_target(NULL, &_PScheduleGroup);
|
|
register_filter(_Filter);
|
|
}
|
|
#endif /* _CRT_USE_WINAPI_FAMILY_DESKTOP_APP */
|
|
|
|
/// <summary>
|
|
/// Destroys the <c>single_assignment</c> messaging block.
|
|
/// </summary>
|
|
/**/
|
|
~single_assignment()
|
|
{
|
|
// Remove all links
|
|
remove_network_links();
|
|
|
|
// Clean up any messages left in this message block
|
|
_Delete_stored_messages();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks whether this <c>single_assignment</c> messaging block has been initialized with a value yet.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// <c>true</c> if the block has received a value, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/**/
|
|
bool has_value() const
|
|
{
|
|
return (_M_pMessage != NULL);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets a reference to the current payload of the message being stored in the <c>single_assignment</c> messaging block.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The payload of the stored message.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// This method will wait until a message arrives if no message is currently stored in the <c>single_assignment</c> messaging block.
|
|
/// </remarks>
|
|
/**/
|
|
_Type const & value()
|
|
{
|
|
if (_M_pMessage == NULL)
|
|
{
|
|
receive<_Type>(this);
|
|
}
|
|
_CONCRT_ASSERT(_M_pMessage != NULL);
|
|
|
|
return _M_pMessage->payload;
|
|
}
|
|
|
|
|
|
protected:
|
|
|
|
/// <summary>
|
|
/// Asynchronously passes a message from an <c>ISource</c> block to this <c>single_assignment</c> messaging block.
|
|
/// It is invoked by the <c>propagate</c> method, when called by a source block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/**/
|
|
virtual message_status propagate_message(_Inout_ message<_Type> * _PMessage, _Inout_ ISource<_Type> * _PSource)
|
|
{
|
|
// It is important that calls to propagate do *not* take the same lock on the
|
|
// internal structure that is used by Consume and the LWT. Doing so could
|
|
// result in a deadlock with the Consume call.
|
|
|
|
message_status _Result = accepted;
|
|
|
|
// single_assignment messaging block can be initialized only once
|
|
if (_M_fIsInitialized)
|
|
{
|
|
return declined;
|
|
}
|
|
|
|
{
|
|
_NR_lock _Lock(_M_propagationLock);
|
|
|
|
if (_M_fIsInitialized)
|
|
{
|
|
_Result = declined;
|
|
}
|
|
else
|
|
{
|
|
_PMessage = _PSource->accept(_PMessage->msg_id(), this);
|
|
|
|
// Set initialized flag only if we have a message
|
|
if (_PMessage != NULL)
|
|
{
|
|
_M_fIsInitialized = true;
|
|
}
|
|
else
|
|
{
|
|
_Result = missed;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If message was accepted, set the member variables for
|
|
// this block and start the asynchronous propagation task
|
|
//
|
|
if (_Result == accepted)
|
|
{
|
|
async_send(_PMessage);
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Synchronously passes a message from an <c>ISource</c> block to this <c>single_assignment</c> messaging block.
|
|
/// It is invoked by the <c>send</c> method, when called by a source block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/**/
|
|
virtual message_status send_message(_Inout_ message<_Type> * _PMessage, _Inout_ ISource<_Type> * _PSource)
|
|
{
|
|
message_status _Result = accepted;
|
|
|
|
// single_assignment messaging block can be initialized only once
|
|
if (_M_fIsInitialized)
|
|
{
|
|
return declined;
|
|
}
|
|
|
|
{
|
|
_NR_lock _Lock(_M_propagationLock);
|
|
|
|
if (_M_fIsInitialized)
|
|
{
|
|
_Result = declined;
|
|
}
|
|
else
|
|
{
|
|
_PMessage = _PSource->accept(_PMessage->msg_id(), this);
|
|
|
|
// Set initialized flag only if we have a message
|
|
if (_PMessage != NULL)
|
|
{
|
|
_M_fIsInitialized = true;
|
|
}
|
|
else
|
|
{
|
|
_Result = missed;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If message was accepted, set the member variables for
|
|
// this block and start the asynchronous propagation task
|
|
//
|
|
if (_Result == accepted)
|
|
{
|
|
sync_send(_PMessage);
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accepts a message that was offered by this <c>single_assignment</c> messaging block,
|
|
/// returning a copy of the message to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the offered <c>message</c> object.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the <c>message</c> object that the caller now has ownership of.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// The <c>single_assignment</c> messaging block returns copies of the message
|
|
/// to its targets, rather than transferring ownership of the currently
|
|
/// held message.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message<_Type> * accept_message(runtime_object_identity _MsgId)
|
|
{
|
|
// This check is to prevent spoofing and verify that the propagated message is
|
|
// the one that is accepted at the end.
|
|
if (_M_pMessage == NULL || _MsgId != _M_pMessage->msg_id())
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Instead of returning the internal message, we return a copy of the
|
|
// message passed in.
|
|
//
|
|
// Because we are returning a copy, the accept routine for a single_assignment
|
|
// does not need to grab the internal lock.
|
|
//
|
|
return (new message<_Type>(_M_pMessage->payload));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reserves a message previously offered by this <c>single_assignment</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being reserved.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the message was successfully reserved, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// After <c>reserve</c> is called, if it returns <c>true</c>, either <c>consume</c> or <c>release</c> must be called
|
|
/// to either take or release ownership of the message.
|
|
/// </remarks>
|
|
/**/
|
|
virtual bool reserve_message(runtime_object_identity _MsgId)
|
|
{
|
|
if (_M_pMessage == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (_M_pMessage->msg_id() != _MsgId)
|
|
{
|
|
throw message_not_found();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Consumes a message previously offered by the <c>single_assignment</c> and reserved by the target,
|
|
/// returning a copy of the message to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being consumed.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the <c>message</c> object that the caller now has ownership of.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// Similar to <c>accept</c>, but is always preceded by a call to <c>reserve</c>.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message<_Type> * consume_message(runtime_object_identity _MsgId)
|
|
{
|
|
_CONCRT_ASSERT(_M_fIsInitialized);
|
|
|
|
return accept_message(_MsgId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases a previous message reservation.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being released.
|
|
/// </param>
|
|
/**/
|
|
virtual void release_message(runtime_object_identity _MsgId)
|
|
{
|
|
_CONCRT_ASSERT(_M_fIsInitialized);
|
|
|
|
if (_M_pMessage == NULL || _M_pMessage->msg_id() != _MsgId)
|
|
{
|
|
throw message_not_found();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resumes propagation after a reservation has been released.
|
|
/// </summary>
|
|
/**/
|
|
virtual void resume_propagation()
|
|
{
|
|
// Because reservation doesn't stop propagation, we don't
|
|
// need to do anything on resume after consume/release.
|
|
}
|
|
|
|
/// <summary>
|
|
/// A callback that notifies that a new target has been linked to this <c>single_assignment</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the newly linked target.
|
|
/// </param>
|
|
/**/
|
|
virtual void link_target_notification(_Inout_ ITarget<_Type> * _PTarget)
|
|
{
|
|
// If there is a message available already, propagate it.
|
|
|
|
if (_M_pMessage != NULL)
|
|
{
|
|
_PTarget->propagate(_M_pMessage, this);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Places the <c>message</c> <paramref name="_PMessage"/> in this <c>single_assignment</c> messaging block and
|
|
/// offers it to all of the linked targets.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to a <c>message</c> that this <c>single_assignment</c> messaging block has taken ownership of.
|
|
/// </param>
|
|
/**/
|
|
virtual void propagate_to_any_targets(_Inout_opt_ message<_Type> * _PMessage)
|
|
{
|
|
// Initialized flag should have been set by the propagate function using interlocked operation.
|
|
_CONCRT_ASSERT(_M_fIsInitialized);
|
|
|
|
// Move the message to the internal storage
|
|
|
|
_CONCRT_ASSERT(_M_pMessage == NULL);
|
|
_M_pMessage = _PMessage;
|
|
|
|
for (target_iterator _Iter = _M_connectedTargets.begin(); *_Iter != NULL; ++_Iter)
|
|
{
|
|
// Single assignment can propagate its message out
|
|
// to any number of Targets
|
|
|
|
ITarget<_Type> * _PTarget = *_Iter;
|
|
_PTarget->propagate(_PMessage, this);
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
/// <summary>
|
|
/// Deletes all messages currently stored in this message block. Should be called
|
|
/// by the destructor to ensure any messages propagated in are cleaned up.
|
|
/// </summary>
|
|
/**/
|
|
void _Delete_stored_messages()
|
|
{
|
|
// Input messages for this message block are in the base-class input buffer
|
|
// All messages in that buffer are guaranteed to have moved to the output
|
|
// buffer because the destructor first waits for all async sends to finish
|
|
// before reaching this point
|
|
|
|
// The messages for a single_assignment are deleted at the end when
|
|
// single_assignment is deleted.
|
|
delete _M_pMessage;
|
|
}
|
|
|
|
//
|
|
// Private Data Members
|
|
//
|
|
|
|
// The message being stored
|
|
message<_Type> * _M_pMessage;
|
|
|
|
// The lock used to protect propagation
|
|
::Concurrency::details::_NonReentrantPPLLock _M_propagationLock;
|
|
|
|
// The marker for whether the single_assignment has already been initialized
|
|
volatile bool _M_fIsInitialized;
|
|
|
|
private:
|
|
//
|
|
// Hide assignment operator and copy constructor
|
|
//
|
|
single_assignment const & operator=(single_assignment const &); // no assignment operator
|
|
single_assignment(single_assignment const &); // no copy constructor
|
|
};
|
|
|
|
//**************************************************************************
|
|
// Join (single-type)
|
|
//**************************************************************************
|
|
|
|
/// <summary>
|
|
/// The type of a <c>join</c> messaging block.
|
|
/// </summary>
|
|
/**/
|
|
enum join_type {
|
|
/// <summary>
|
|
/// Greedy <c>join</c> messaging blocks immediately accept a message upon propagation. This is more efficient,
|
|
/// but has the possibility for live-lock, depending on the network configuration.
|
|
/// </summary>
|
|
/**/
|
|
greedy = 0,
|
|
/// <summary>
|
|
/// Non-greedy <c>join</c> messaging blocks postpone messages and try and consume them after all have arrived.
|
|
/// These are guaranteed to work, but slower.
|
|
/// </summary>
|
|
/**/
|
|
non_greedy = 1
|
|
};
|
|
|
|
/// <summary>
|
|
/// A <c>join</c> messaging block is a single-target, multi-source, ordered
|
|
/// <c>propagator_block</c> which combines together messages of type <typeparamref name="_Type"/> from each
|
|
/// of its sources.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload type of the messages joined and propagated by the block.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Jtype">
|
|
/// The kind of <c>join</c> block this is, either <c>greedy</c> or <c>non_greedy</c>
|
|
/// </typeparam>
|
|
/// <remarks>
|
|
/// For more information, see <see cref="Asynchronous Message Blocks"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="choice Class"/>
|
|
/// <seealso cref="multitype_join Class"/>
|
|
/// <seealso cref="join_type Enumeration"/>
|
|
/**/
|
|
template<class _Type, join_type _Jtype = non_greedy>
|
|
class join : public propagator_block<single_link_registry<ITarget<std::vector<_Type>>>, multi_link_registry<ISource<_Type>>>
|
|
{
|
|
public:
|
|
typedef typename std::vector<_Type> _OutputType;
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>join</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_NumInputs">
|
|
/// The number of inputs this <c>join</c> block will be allowed.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>join</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
join(size_t _NumInputs)
|
|
: _M_messageArray(_NumInputs),
|
|
_M_savedMessageIdArray(_NumInputs)
|
|
{
|
|
_Initialize(_NumInputs);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>join</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_NumInputs">
|
|
/// The number of inputs this <c>join</c> block will be allowed.
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A filter function which determines whether offered messages should be accepted.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>join</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
join(size_t _NumInputs, filter_method const& _Filter)
|
|
: _M_messageArray(_NumInputs),
|
|
_M_savedMessageIdArray(_NumInputs)
|
|
{
|
|
_Initialize(_NumInputs);
|
|
register_filter(_Filter);
|
|
}
|
|
|
|
#ifdef _CRT_USE_WINAPI_FAMILY_DESKTOP_APP
|
|
/// <summary>
|
|
/// Constructs a <c>join</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// The <c>Scheduler</c> object within which the propagation task for the <c>join</c> messaging block is scheduled.
|
|
/// </param>
|
|
/// <param name="_NumInputs">
|
|
/// The number of inputs this <c>join</c> block will be allowed.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>join</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
join(Scheduler& _PScheduler, size_t _NumInputs)
|
|
: _M_messageArray(_NumInputs),
|
|
_M_savedMessageIdArray(_NumInputs)
|
|
{
|
|
_Initialize(_NumInputs, &_PScheduler);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>join</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// The <c>Scheduler</c> object within which the propagation task for the <c>join</c> messaging block is scheduled.
|
|
/// </param>
|
|
/// <param name="_NumInputs">
|
|
/// The number of inputs this <c>join</c> block will be allowed.
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A filter function which determines whether offered messages should be accepted.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>join</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
join(Scheduler& _PScheduler, size_t _NumInputs, filter_method const& _Filter)
|
|
: _M_messageArray(_NumInputs),
|
|
_M_savedMessageIdArray(_NumInputs)
|
|
{
|
|
_Initialize(_NumInputs, &_PScheduler);
|
|
register_filter(_Filter);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>join</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The <c>ScheduleGroup</c> object within which the propagation task for the <c>join</c> messaging block is scheduled.
|
|
/// The <c>Scheduler</c> object used is implied by the schedule group.
|
|
/// </param>
|
|
/// <param name="_NumInputs">
|
|
/// The number of inputs this <c>join</c> block will be allowed.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>join</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
join(ScheduleGroup& _PScheduleGroup, size_t _NumInputs)
|
|
: _M_messageArray(_NumInputs),
|
|
_M_savedMessageIdArray(_NumInputs)
|
|
{
|
|
_Initialize(_NumInputs, NULL, &_PScheduleGroup);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>join</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The <c>ScheduleGroup</c> object within which the propagation task for the <c>join</c> messaging block is scheduled.
|
|
/// The <c>Scheduler</c> object used is implied by the schedule group.
|
|
/// </param>
|
|
/// <param name="_NumInputs">
|
|
/// The number of inputs this <c>join</c> block will be allowed.
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A filter function which determines whether offered messages should be accepted.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// <para>The type <typeparamref name="filter_method"/> is a functor with signature <c>bool (_Type const &)</c>
|
|
/// which is invoked by this <c>join</c> messaging block to determine whether or not it should accept
|
|
/// an offered message.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
join(ScheduleGroup& _PScheduleGroup, size_t _NumInputs, filter_method const& _Filter)
|
|
: _M_messageArray(_NumInputs),
|
|
_M_savedMessageIdArray(_NumInputs)
|
|
{
|
|
_Initialize(_NumInputs, NULL, &_PScheduleGroup);
|
|
register_filter(_Filter);
|
|
}
|
|
#endif /* _CRT_USE_WINAPI_FAMILY_DESKTOP_APP */
|
|
|
|
/// <summary>
|
|
/// Destroys the <c>join</c> block.
|
|
/// </summary>
|
|
/**/
|
|
~join()
|
|
{
|
|
// Remove all links that are targets of this join
|
|
remove_network_links();
|
|
|
|
// Clean up any messages left in this message block
|
|
_Delete_stored_messages();
|
|
|
|
delete [] _M_savedIdBuffer;
|
|
}
|
|
|
|
protected:
|
|
//
|
|
// propagator_block protected function implementations
|
|
//
|
|
|
|
/// <summary>
|
|
/// Asynchronously passes a message from an <c>ISource</c> block to this <c>join</c> messaging block.
|
|
/// It is invoked by the <c>propagate</c> method, when called by a source block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/**/
|
|
message_status propagate_message(_Inout_ message<_Type> * _PMessage, _Inout_ ISource<_Type> * _PSource)
|
|
{
|
|
// It is important that calls to propagate do *not* take the same lock on the
|
|
// internal structure that is used by Consume and the LWT. Doing so could
|
|
// result in a deadlock with the Consume call.
|
|
|
|
message_status _Ret_val = accepted;
|
|
|
|
//
|
|
// Find the slot index of this source
|
|
//
|
|
size_t _Slot = 0;
|
|
bool _Found = false;
|
|
for (source_iterator _Iter = _M_connectedSources.begin(); *_Iter != NULL; ++_Iter)
|
|
{
|
|
if (*_Iter == _PSource)
|
|
{
|
|
_Found = true;
|
|
break;
|
|
}
|
|
|
|
_Slot++;
|
|
}
|
|
|
|
if (!_Found)
|
|
{
|
|
// If this source was not found in the array, this is not a connected source
|
|
// decline the message
|
|
return declined;
|
|
}
|
|
|
|
_CONCRT_ASSERT(_Slot < _M_messageArray._M_count);
|
|
|
|
bool fIsGreedy = (_Jtype == greedy);
|
|
|
|
if (fIsGreedy)
|
|
{
|
|
//
|
|
// Greedy type joins immediately accept the message.
|
|
//
|
|
{
|
|
_NR_lock lockHolder(_M_propagationLock);
|
|
if (_M_messageArray._M_messages[_Slot] != NULL)
|
|
{
|
|
_M_savedMessageIdArray._M_savedIds[_Slot] = _PMessage->msg_id();
|
|
_Ret_val = postponed;
|
|
}
|
|
}
|
|
|
|
if (_Ret_val != postponed)
|
|
{
|
|
_M_messageArray._M_messages[_Slot] = _PSource->accept(_PMessage->msg_id(), this);
|
|
|
|
if (_M_messageArray._M_messages[_Slot] != NULL)
|
|
{
|
|
if (_InterlockedDecrementSizeT(&_M_messagesRemaining) == 0)
|
|
{
|
|
// If messages have arrived on all links, start a propagation
|
|
// of the current message
|
|
async_send(NULL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_Ret_val = missed;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Non-greedy type joins save the message IDs until they have all arrived
|
|
//
|
|
|
|
if (_InterlockedExchange((volatile long *) &_M_savedMessageIdArray._M_savedIds[_Slot], _PMessage->msg_id()) == -1)
|
|
{
|
|
// Decrement the message remaining count if this thread is switching
|
|
// the saved ID from -1 to a valid value.
|
|
if (_InterlockedDecrementSizeT(&_M_messagesRemaining) == 0)
|
|
{
|
|
async_send(NULL);
|
|
}
|
|
}
|
|
|
|
// Always return postponed. This message will be consumed
|
|
// in the LWT
|
|
_Ret_val = postponed;
|
|
}
|
|
|
|
return _Ret_val;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accepts a message that was offered by this <c>join</c> messaging block,
|
|
/// transferring ownership to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the offered <c>message</c> object.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the <c>message</c> object that the caller now has ownership of.
|
|
/// </returns>
|
|
/**/
|
|
virtual message<_OutputType> * accept_message(runtime_object_identity _MsgId)
|
|
{
|
|
//
|
|
// Peek at the head message in the message buffer. If the IDs match
|
|
// dequeue and transfer ownership
|
|
//
|
|
message<_OutputType> * _Msg = NULL;
|
|
|
|
if (_M_messageBuffer._Is_head(_MsgId))
|
|
{
|
|
_Msg = _M_messageBuffer._Dequeue();
|
|
}
|
|
|
|
return _Msg;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reserves a message previously offered by this <c>join</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the offered <c>message</c> object.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the message was successfully reserved, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// After <c>reserve</c> is called, if it returns <c>true</c>, either <c>consume</c> or <c>release</c> must be called
|
|
/// to either take or release ownership of the message.
|
|
/// </remarks>
|
|
/**/
|
|
virtual bool reserve_message(runtime_object_identity _MsgId)
|
|
{
|
|
// Allow reservation if this is the head message
|
|
return _M_messageBuffer._Is_head(_MsgId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Consumes a message previously offered by the <c>join</c> messaging block and reserved by the target,
|
|
/// transferring ownership to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being consumed.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the <c>message</c> object that the caller now has ownership of.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// Similar to <c>accept</c>, but is always preceded by a call to <c>reserve</c>.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message<_OutputType> * consume_message(runtime_object_identity _MsgId)
|
|
{
|
|
// By default, accept the message
|
|
return accept_message(_MsgId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases a previous message reservation.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being released.
|
|
/// </param>
|
|
/**/
|
|
virtual void release_message(runtime_object_identity _MsgId)
|
|
{
|
|
// The head message is the one reserved.
|
|
if (!_M_messageBuffer._Is_head(_MsgId))
|
|
{
|
|
throw message_not_found();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resumes propagation after a reservation has been released.
|
|
/// </summary>
|
|
/**/
|
|
virtual void resume_propagation()
|
|
{
|
|
// If there are any messages in the buffer, propagate them out
|
|
if (_M_messageBuffer._Count() > 0)
|
|
{
|
|
async_send(NULL);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A callback that notifies that a new target has been linked to this <c>join</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the newly linked target.
|
|
/// </param>
|
|
/**/
|
|
virtual void link_target_notification(_Inout_ ITarget<std::vector<_Type>> *)
|
|
{
|
|
// If the message queue is blocked due to reservation
|
|
// there is no need to do any message propagation
|
|
if (_M_pReservedFor != NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_Propagate_priority_order(_M_messageBuffer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs an output message containing an input message from each source when
|
|
/// they have all propagated a message. Sends this output message out to each of
|
|
/// its targets.
|
|
/// </summary>
|
|
/**/
|
|
void propagate_to_any_targets(_Inout_opt_ message<_OutputType> *)
|
|
{
|
|
message<_OutputType> * _Msg = NULL;
|
|
// Create a new message from the input sources
|
|
// If messagesRemaining == 0, we have a new message to create. Otherwise, this is coming from
|
|
// a consume or release from the target. In that case we don't want to create a new message.
|
|
if (_M_messagesRemaining == 0)
|
|
{
|
|
// A greedy join can immediately create the message, a non-greedy
|
|
// join must try and consume all the messages it has postponed
|
|
_Msg = _Create_new_message();
|
|
}
|
|
|
|
if (_Msg == NULL)
|
|
{
|
|
// Create message failed. This happens in non_greedy joins when the
|
|
// reserve/consumption of a postponed message failed.
|
|
_Propagate_priority_order(_M_messageBuffer);
|
|
return;
|
|
}
|
|
|
|
bool fIsGreedy = (_Jtype == greedy);
|
|
|
|
// For a greedy join, reset the number of messages remaining
|
|
// Check to see if multiple messages have been passed in on any of the links,
|
|
// and postponed. If so, try and reserve/consume them now
|
|
if (fIsGreedy)
|
|
{
|
|
// Look at the saved IDs and reserve/consume any that have passed in while
|
|
// this join was waiting to complete
|
|
_CONCRT_ASSERT(_M_messageArray._M_count == _M_savedMessageIdArray._M_count);
|
|
|
|
for (size_t i = 0; i < _M_messageArray._M_count; i++)
|
|
{
|
|
for(;;)
|
|
{
|
|
runtime_object_identity _Saved_id;
|
|
// Grab the current saved ID value. This value could be changing from based on any
|
|
// calls of source->propagate(this). If the message ID is different than what is snapped
|
|
// here, that means, the reserve below must fail. This is because reserve is trying
|
|
// to get the same source lock the propagate(this) call must be holding.
|
|
{
|
|
_NR_lock lockHolder(_M_propagationLock);
|
|
|
|
_CONCRT_ASSERT(_M_messageArray._M_messages[i] != NULL);
|
|
|
|
_Saved_id = _M_savedMessageIdArray._M_savedIds[i];
|
|
|
|
if (_Saved_id == -1)
|
|
{
|
|
_M_messageArray._M_messages[i] = NULL;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
_M_savedMessageIdArray._M_savedIds[i] = -1;
|
|
}
|
|
}
|
|
|
|
if (_Saved_id != -1)
|
|
{
|
|
source_iterator _Iter = _M_connectedSources.begin();
|
|
|
|
ISource<_Type> * _PSource = _Iter[i];
|
|
if ((_PSource != NULL) && _PSource->reserve(_Saved_id, this))
|
|
{
|
|
_M_messageArray._M_messages[i] = _PSource->consume(_Saved_id, this);
|
|
_InterlockedDecrementSizeT(&_M_messagesRemaining);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If messages have all been received, async_send again, this will start the
|
|
// LWT up to create a new message
|
|
if (_M_messagesRemaining == 0)
|
|
{
|
|
async_send(NULL);
|
|
}
|
|
}
|
|
|
|
// Add the new message to the outbound queue
|
|
_M_messageBuffer._Enqueue(_Msg);
|
|
|
|
if (!_M_messageBuffer._Is_head(_Msg->msg_id()))
|
|
{
|
|
// another message is at the head of the outbound message queue and blocked
|
|
// simply return
|
|
return;
|
|
}
|
|
|
|
_Propagate_priority_order(_M_messageBuffer);
|
|
}
|
|
|
|
private:
|
|
|
|
//
|
|
// Private Methods
|
|
//
|
|
|
|
/// <summary>
|
|
/// Propagate messages in priority order.
|
|
/// </summary>
|
|
/// <param name="_MessageBuffer">
|
|
/// Reference to a message queue with messages to be propagated
|
|
/// </param>
|
|
/**/
|
|
void _Propagate_priority_order(::Concurrency::details::_Queue<message<_Target_type>> & _MessageBuffer)
|
|
{
|
|
message<_Target_type> * _Msg = _MessageBuffer._Peek();
|
|
|
|
// If someone has reserved the _Head message, don't propagate anymore
|
|
if (_M_pReservedFor != NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
while (_Msg != NULL)
|
|
{
|
|
message_status _Status = declined;
|
|
|
|
// Always start from the first target that linked
|
|
for (target_iterator _Iter = _M_connectedTargets.begin(); *_Iter != NULL; ++_Iter)
|
|
{
|
|
ITarget<_Target_type> * _PTarget = *_Iter;
|
|
_Status = _PTarget->propagate(_Msg, this);
|
|
|
|
// Ownership of message changed. Do not propagate this
|
|
// message to any other target.
|
|
if (_Status == accepted)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// If the target just propagated to reserved this message, stop
|
|
// propagating it to others
|
|
if (_M_pReservedFor != NULL)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If status is anything other than accepted, then the head message
|
|
// was not propagated out. Thus, nothing after it in the queue can
|
|
// be propagated out. Cease propagation.
|
|
if (_Status != accepted)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Get the next message
|
|
_Msg = _MessageBuffer._Peek();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a new message from the data output.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The created message (NULL if creation failed)
|
|
/// </returns>
|
|
/**/
|
|
message<std::vector<_Type>> * __cdecl _Create_new_message()
|
|
{
|
|
bool fIsNonGreedy = (_Jtype == non_greedy);
|
|
|
|
// If this is a non-greedy join, check each source and try to consume their message
|
|
if (fIsNonGreedy)
|
|
{
|
|
|
|
// The iterator _Iter below will ensure that it is safe to touch
|
|
// non-NULL source pointers. Take a snapshot.
|
|
std::vector<ISource<_Type> *> _Sources;
|
|
source_iterator _Iter = _M_connectedSources.begin();
|
|
|
|
while (*_Iter != NULL)
|
|
{
|
|
ISource<_Type> * _PSource = *_Iter;
|
|
|
|
if (_PSource == NULL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
_Sources.push_back(_PSource);
|
|
++_Iter;
|
|
}
|
|
|
|
if (_Sources.size() != _M_messageArray._M_count)
|
|
{
|
|
// Some of the sources were unlinked. The join is broken
|
|
return NULL;
|
|
}
|
|
|
|
// First, try and reserve all the messages. If a reservation fails,
|
|
// then release any reservations that had been made.
|
|
for (size_t i = 0; i < _M_savedMessageIdArray._M_count; i++)
|
|
{
|
|
// Snap the current saved ID into a buffer. This value can be changing behind the scenes from
|
|
// other source->propagate(msg, this) calls, but if so, that just means the reserve below will
|
|
// fail.
|
|
_InterlockedIncrementSizeT(&_M_messagesRemaining);
|
|
_M_savedIdBuffer[i] = _InterlockedExchange((volatile long *) &_M_savedMessageIdArray._M_savedIds[i], -1);
|
|
|
|
_CONCRT_ASSERT(_M_savedIdBuffer[i] != -1);
|
|
|
|
if (!_Sources[i]->reserve(_M_savedIdBuffer[i], this))
|
|
{
|
|
// A reservation failed, release all reservations made up until
|
|
// this block, and wait for another message to arrive on this link
|
|
for (size_t j = 0; j < i; j++)
|
|
{
|
|
_Sources[j]->release(_M_savedIdBuffer[j], this);
|
|
if (_InterlockedCompareExchange((volatile long *) &_M_savedMessageIdArray._M_savedIds[j], _M_savedIdBuffer[j], -1) == -1)
|
|
{
|
|
if (_InterlockedDecrementSizeT(&_M_messagesRemaining) == 0)
|
|
{
|
|
async_send(NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return NULL to indicate that the create failed
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// Because everything has been reserved, consume all the messages.
|
|
// This is guaranteed to return true.
|
|
for (size_t i = 0; i < _M_messageArray._M_count; i++)
|
|
{
|
|
_M_messageArray._M_messages[i] = _Sources[i]->consume(_M_savedIdBuffer[i], this);
|
|
_M_savedIdBuffer[i] = -1;
|
|
}
|
|
}
|
|
|
|
if (!fIsNonGreedy)
|
|
{
|
|
// Reinitialize how many messages are being waited for.
|
|
// This is safe because all messages have been received, thus no new async_sends for
|
|
// greedy joins can be called.
|
|
_M_messagesRemaining = _M_messageArray._M_count;
|
|
}
|
|
|
|
std::vector<_Type> _OutputVector;
|
|
for (size_t i = 0; i < _M_messageArray._M_count; i++)
|
|
{
|
|
_CONCRT_ASSERT(_M_messageArray._M_messages[i] != NULL);
|
|
_OutputVector.push_back(_M_messageArray._M_messages[i]->payload);
|
|
|
|
delete _M_messageArray._M_messages[i];
|
|
if (fIsNonGreedy)
|
|
{
|
|
_M_messageArray._M_messages[i] = NULL;
|
|
}
|
|
}
|
|
return (new message<std::vector<_Type>>(_OutputVector));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes the <c>join</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_NumInputs">
|
|
/// The number of inputs.
|
|
/// </param>
|
|
/// <param name="_PScheduler">
|
|
/// The scheduler onto which the task to propagate the <c>join</c> block's message will be scheduled.
|
|
/// If unspecified, the <c>join</c> messaging block uses the default scheduler.
|
|
/// </param>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The schedule group into which the task to propagate the <c>join</c> block's message will be scheduled.
|
|
/// The scheduler used is implied by the schedule group. If unspecified, the <c>join</c> uses a schedule
|
|
/// group of the scheduler's choosing.
|
|
/// </param>
|
|
/**/
|
|
void _Initialize(size_t _NumInputs, Scheduler * _PScheduler = NULL, ScheduleGroup * _PScheduleGroup = NULL)
|
|
{
|
|
initialize_source_and_target(_PScheduler, _PScheduleGroup);
|
|
|
|
_M_connectedSources.set_bound(_NumInputs);
|
|
_M_messagesRemaining = _NumInputs;
|
|
|
|
bool fIsNonGreedy = (_Jtype == non_greedy);
|
|
|
|
if (fIsNonGreedy)
|
|
{
|
|
// Non greedy joins need a buffer to snap off saved message IDs to.
|
|
_M_savedIdBuffer = new runtime_object_identity[_NumInputs];
|
|
memset(_M_savedIdBuffer, -1, sizeof(runtime_object_identity) * _NumInputs);
|
|
}
|
|
else
|
|
{
|
|
_M_savedIdBuffer = NULL;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes all messages currently stored in this message block. Should be called
|
|
/// by the destructor to ensure any messages propagated in are cleaned up.
|
|
/// </summary>
|
|
/**/
|
|
void _Delete_stored_messages()
|
|
{
|
|
// Input messages for this message block are in the base-class input buffer
|
|
// All messages in that buffer are guaranteed to have moved to the output
|
|
// buffer because the destructor first waits for all async sends to finish
|
|
// before reaching this point
|
|
|
|
// Delete any messages remaining in the output queue
|
|
for (;;)
|
|
{
|
|
message<std::vector<_Type>> * _Msg = _M_messageBuffer._Dequeue();
|
|
if (_Msg == NULL)
|
|
{
|
|
break;
|
|
}
|
|
delete _Msg;
|
|
}
|
|
}
|
|
|
|
// The current number of messages remaining
|
|
volatile size_t _M_messagesRemaining;
|
|
|
|
// An array containing the accepted messages of this join.
|
|
// Wrapped in a struct to enable debugger visualization.
|
|
struct _MessageArray
|
|
{
|
|
size_t _M_count;
|
|
message<_Type>** _M_messages;
|
|
|
|
_MessageArray(size_t _NumInputs)
|
|
: _M_count(_NumInputs),
|
|
_M_messages(new message<_Type>*[_NumInputs])
|
|
{
|
|
memset(_M_messages, 0, sizeof(message<_Type> *) * _NumInputs);
|
|
}
|
|
|
|
~_MessageArray()
|
|
{
|
|
for (size_t i = 0; i < _M_count; i++)
|
|
delete _M_messages[i];
|
|
delete [] _M_messages;
|
|
}
|
|
};
|
|
_MessageArray _M_messageArray;
|
|
|
|
// An array containing the msg IDs of messages propagated to the array
|
|
// For greedy joins, this contains a log of other messages passed to this
|
|
// join after the first has been accepted
|
|
// For non-greedy joins, this contains the message ID of any message
|
|
// passed to it.
|
|
// Wrapped in a struct to enable debugger visualization.
|
|
struct _SavedMessageIdArray
|
|
{
|
|
size_t _M_count;
|
|
runtime_object_identity * _M_savedIds;
|
|
|
|
_SavedMessageIdArray(size_t _NumInputs)
|
|
: _M_count(_NumInputs),
|
|
_M_savedIds(new runtime_object_identity[_NumInputs])
|
|
{
|
|
memset(_M_savedIds, -1, sizeof(runtime_object_identity) * _NumInputs);
|
|
}
|
|
|
|
~_SavedMessageIdArray()
|
|
{
|
|
delete [] _M_savedIds;
|
|
}
|
|
};
|
|
_SavedMessageIdArray _M_savedMessageIdArray;
|
|
|
|
// Buffer for snapping saved IDs in non-greedy joins
|
|
runtime_object_identity * _M_savedIdBuffer;
|
|
|
|
// A lock for modifying the buffer or the connected blocks
|
|
::Concurrency::details::_NonReentrantPPLLock _M_propagationLock;
|
|
|
|
// Queue to hold output messages
|
|
::Concurrency::details::_Queue<message<std::vector<_Type>>> _M_messageBuffer;
|
|
};
|
|
|
|
|
|
//**************************************************************************
|
|
// Multi-Type Choice and Join helper node:
|
|
//**************************************************************************
|
|
|
|
/// <summary>
|
|
/// Base class for Helper node used in multi-type join and choice blocks
|
|
/// Order node is a single-target, single-source ordered propagator block
|
|
/// The main property of an order node is that it accepts a message of _Type
|
|
/// and outputs a message of int, with some unique assigned index number.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload type
|
|
/// </typeparam>
|
|
/**/
|
|
template<class _Type>
|
|
class _Order_node_base: public propagator_block<single_link_registry<ITarget<size_t>>, multi_link_registry<ISource<_Type>>>
|
|
{
|
|
public:
|
|
|
|
/// <summary>
|
|
/// Constructs a _Order_node_base within the default scheduler, and places it on any schedule
|
|
/// group of the scheduler's choosing.
|
|
/// </summary>
|
|
/**/
|
|
_Order_node_base() :
|
|
_M_index(0),
|
|
_M_pReceiveMessage(NULL),
|
|
_M_pSendMessage(NULL)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleans up any resources that may have been created by the _Order_node.
|
|
/// </summary>
|
|
/**/
|
|
~_Order_node_base()
|
|
{
|
|
// The messages for an _Order_node_base are deleted at the end when
|
|
// _Order_node_base is deleted.
|
|
delete _M_pReceiveMessage;
|
|
delete _M_pSendMessage;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks whether this block has been initialized yet.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// true, if the block has received a value, false otherwise.
|
|
/// </returns>
|
|
/**/
|
|
bool has_value() const
|
|
{
|
|
return (_M_pReceiveMessage != NULL);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a reference to the current payload of the message being stored.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The incoming payload.
|
|
/// </returns>
|
|
/**/
|
|
_Type const & value()
|
|
{
|
|
_CONCRT_ASSERT(_M_pReceiveMessage != NULL);
|
|
|
|
return _M_pReceiveMessage->payload;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the _Order_node_base and prepares it for the next propagation
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// _Reset is called from Populate_destination_tuple through propagate_to_any_targets()
|
|
/// thus, it always has the internal lock held. This is only used for _Greedy_node and
|
|
/// _Non_greedy_node.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void _Reset() = 0;
|
|
|
|
/// <summary>
|
|
/// Reserves a message previously offered by the source.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The runtime object identity of the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A bool indicating whether the reservation worked or not
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// After 'reserve' is called, either 'consume' or 'release' must be called.
|
|
/// </remarks>
|
|
/**/
|
|
virtual bool reserve_message(runtime_object_identity)
|
|
{
|
|
// reserve should never be called for this block.
|
|
_CONCRT_ASSERT(false);
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Consumes a message previously offered by the source and reserved by the target,
|
|
/// transferring ownership to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The runtime object identity of the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the message that the caller now has ownership of.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// Similar to 'accept', but is always preceded by a call to 'reserve'
|
|
/// </remarks>
|
|
/**/
|
|
virtual message<size_t> * consume_message(runtime_object_identity)
|
|
{
|
|
// consume should never be called for this block.
|
|
_CONCRT_ASSERT(false);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases a previous message reservation.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The runtime object identity of the message.
|
|
/// </param>
|
|
/**/
|
|
virtual void release_message(runtime_object_identity)
|
|
{
|
|
// release should never be called for this block.
|
|
_CONCRT_ASSERT(false);
|
|
}
|
|
|
|
protected:
|
|
|
|
|
|
/// <summary>
|
|
/// Resumes propagation after a reservation has been released
|
|
/// </summary>
|
|
/**/
|
|
virtual void resume_propagation()
|
|
{
|
|
// Because there is only a single target, nothing needs
|
|
// to be done on resume
|
|
}
|
|
|
|
/// <summary>
|
|
/// Notification that a target was linked to this source.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the newly linked target.
|
|
/// </param>
|
|
/**/
|
|
virtual void link_target_notification(_Inout_ ITarget<size_t> *)
|
|
{
|
|
if (_M_pSendMessage != NULL)
|
|
{
|
|
propagate_to_any_targets(NULL);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a message that contains an index used to determine the source message
|
|
/// </summary>
|
|
/**/
|
|
void _Create_send_message()
|
|
{
|
|
_M_pSendMessage = new message<size_t>(_M_index);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate constructor arguments and fully connect this _Order_node_base.
|
|
/// </summary>
|
|
/**/
|
|
void _Initialize_order_node(ISource<_Type> * _PSource, size_t _Index, ITarget<size_t> * _PTarget, Scheduler * _PScheduler = NULL, ScheduleGroup * _PScheduleGroup = NULL)
|
|
{
|
|
if (_Index < 0)
|
|
{
|
|
throw std::invalid_argument("_Index");
|
|
}
|
|
|
|
if (_PSource == NULL)
|
|
{
|
|
throw std::invalid_argument("_PSource");
|
|
}
|
|
|
|
_M_index = _Index;
|
|
|
|
initialize_source_and_target(_PScheduler, _PScheduleGroup);
|
|
|
|
// Allow only a single source and ensure that they
|
|
// cannot be unlinked and relinked.
|
|
_M_connectedSources.set_bound(1);
|
|
|
|
if (_PTarget != NULL)
|
|
{
|
|
link_target(_PTarget);
|
|
}
|
|
|
|
_PSource->link_target(this);
|
|
}
|
|
|
|
//
|
|
// Private Data Members
|
|
//
|
|
|
|
// The message to be received from the source
|
|
message<_Type> * _M_pReceiveMessage;
|
|
|
|
// The message to be sent to all targets
|
|
message<size_t> * _M_pSendMessage;
|
|
|
|
// The index of the _Order_node_base in the user's construct
|
|
size_t _M_index;
|
|
|
|
private:
|
|
//
|
|
// Hide assignment operator and copy constructor
|
|
//
|
|
_Order_node_base const & operator=(_Order_node_base const &); // no assignment operator
|
|
_Order_node_base(_Order_node_base const &); // no copy constructor
|
|
};
|
|
|
|
|
|
/// <summary>
|
|
/// Helper class used in multi-type choice blocks
|
|
/// Ordered node is a single-target, single-source ordered propagator block
|
|
/// </summary>
|
|
///
|
|
/// <typeparam name="_Type">
|
|
/// The payload type
|
|
/// </typeparam>
|
|
/**/
|
|
template<class _Type>
|
|
class _Reserving_node: public _Order_node_base<_Type>
|
|
{
|
|
public:
|
|
/// <summary>
|
|
/// Constructs a _Reserving_node within the default scheduler, and places it on any schedule
|
|
/// group of the scheduler's choosing.
|
|
/// </summary>
|
|
/// <param name="_PSource">
|
|
/// The source of data passed into the node
|
|
/// </param>
|
|
/// <param name="_Index">
|
|
/// The node's index, assigned from the outside.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the node will signal about having received its input data
|
|
/// </param>
|
|
/**/
|
|
_Reserving_node(ISource<_Type> * _PSource, size_t _Index, ITarget<size_t> * _PTarget = NULL) :
|
|
_M_fIsInitialized(false),
|
|
_M_savedId(-1),
|
|
_M_pReservedSource(NULL)
|
|
{
|
|
_Initialize_order_node(_PSource, _Index, _PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a _Reserving_node within the default scheduler, and places it on any schedule
|
|
/// group of the scheduler's choosing.
|
|
/// </summary>
|
|
/// <param name="_PSource">
|
|
/// The source of data passed into the node
|
|
/// </param>
|
|
/// <param name="_Index">
|
|
/// The node's index, assigned from the outside.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the node will signal about having received its input data
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A reference to a filter function.
|
|
/// </param>
|
|
/**/
|
|
_Reserving_node(ISource<_Type> * _PSource, size_t _Index, ITarget<size_t> * _PTarget, filter_method const& _Filter) :
|
|
_M_fIsInitialized(false),
|
|
_M_savedId(-1),
|
|
_M_pReservedSource(NULL)
|
|
{
|
|
register_filter(_Filter);
|
|
_Initialize_order_node(_PSource, _Index, _PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a _Reserving_node within the specified scheduler, and places it on any schedule
|
|
/// group of the scheduler's choosing.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// A reference to a scheduler instance.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// The source of data passed into the node
|
|
/// </param>
|
|
/// <param name="_Index">
|
|
/// The node's index, assigned from the outside.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the node will signal about having received its input data
|
|
/// </param>
|
|
/**/
|
|
_Reserving_node(Scheduler& _PScheduler, ISource<_Type> * _PSource, size_t _Index, ITarget<size_t> * _PTarget = NULL) :
|
|
_M_fIsInitialized(false),
|
|
_M_savedId(-1),
|
|
_M_pReservedSource(NULL)
|
|
{
|
|
_Initialize_order_node(_PSource, _Index, _PTarget, &_PScheduler);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a _Reserving_node within the specified scheduler, and places it on any schedule
|
|
/// group of the scheduler's choosing.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// A reference to a scheduler instance.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// The source of data passed into the node
|
|
/// </param>
|
|
/// <param name="_Index">
|
|
/// The node's index, assigned from the outside.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the node will signal about having received its input data
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A reference to a filter function.
|
|
/// </param>
|
|
/**/
|
|
_Reserving_node(Scheduler& _PScheduler, ISource<_Type> * _PSource, size_t _Index, ITarget<size_t> * _PTarget, filter_method const& _Filter) :
|
|
_M_fIsInitialized(false),
|
|
_M_savedId(-1),
|
|
_M_pReservedSource(NULL)
|
|
{
|
|
register_filter(_Filter);
|
|
_Initialize_order_node(_PSource, _Index, _PTarget, &_PScheduler);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a _Order_node within the specified schedule group. The scheduler is implied
|
|
/// by the schedule group.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// A reference to a schedule group.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// The source of data passed into the node
|
|
/// </param>
|
|
/// <param name="_Index">
|
|
/// The node's index, assigned from the outside.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the node will signal about having received its input data
|
|
/// </param>
|
|
/**/
|
|
_Reserving_node(ScheduleGroup& _PScheduleGroup, ISource<_Type> * _PSource, size_t _Index, ITarget<size_t> * _PTarget = NULL) :
|
|
_M_fIsInitialized(false),
|
|
_M_savedId(-1),
|
|
_M_pReservedSource(NULL)
|
|
{
|
|
_Initialize_order_node(_PSource, _Index, _PTarget, NULL, &_PScheduleGroup);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a _Order_node within the specified schedule group. The scheduler is implied
|
|
/// by the schedule group.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// A reference to a schedule group.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// The source of data passed into the node
|
|
/// </param>
|
|
/// <param name="_Index">
|
|
/// The node's index, assigned from the outside.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the node will signal about having received its input data
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A reference to a filter function.
|
|
/// </param>
|
|
/**/
|
|
_Reserving_node(ScheduleGroup& _PScheduleGroup, ISource<_Type> * _PSource, size_t _Index, ITarget<size_t> * _PTarget, filter_method const& _Filter) :
|
|
_M_fIsInitialized(false),
|
|
_M_savedId(-1),
|
|
_M_pReservedSource(NULL)
|
|
{
|
|
register_filter(_Filter);
|
|
_Initialize_order_node(_PSource, _Index, _PTarget, NULL, &_PScheduleGroup);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleans up any resources that may have been created by the _Reserving_node.
|
|
/// </summary>
|
|
/**/
|
|
~_Reserving_node()
|
|
{
|
|
if (_M_pReservedSource != NULL)
|
|
{
|
|
_M_pReservedSource = NULL;
|
|
_M_connectedSources.release();
|
|
}
|
|
|
|
// Remove all links
|
|
remove_network_links();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Resets the _Reserving_node and prepares it for the next propagation
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This function is not used in a _Reserving_node, which is only used for choice blocks
|
|
/// </remarks>
|
|
/**/
|
|
virtual void _Reset()
|
|
{
|
|
}
|
|
|
|
protected:
|
|
|
|
//
|
|
// propagator_block protected function implementation
|
|
//
|
|
|
|
/// <summary>
|
|
/// Asynchronously passes a message from an <c>ISource</c> block to this <c>ITarget</c> block. It is invoked
|
|
/// by the <c>propagate</c> method, when called by a source block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// It is important that calls to propagate do *not* take the same lock on the
|
|
/// internal structure that is used by Consume and the light-weight task. Doing so could
|
|
/// result in a deadlock with the Consume call.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message_status propagate_message(message<_Type> * _PMessage, ISource<_Type> * _PSource)
|
|
{
|
|
message_status _Result = postponed;
|
|
|
|
// _Order_node messaging block can be initialized only once, just like single_assignment.
|
|
if (_M_fIsInitialized)
|
|
{
|
|
return declined;
|
|
}
|
|
|
|
// Reserve a message on the source until this _Order_node gets the feedback from
|
|
// the single_assignment on whether it has been selected.
|
|
_M_fIsInitialized = _PSource->reserve(_PMessage->msg_id(), this);
|
|
|
|
//
|
|
// If message was successfully reserved, set the member variables for
|
|
// this messaging block and start the asynchronous propagation task.
|
|
//
|
|
if (_M_fIsInitialized)
|
|
{
|
|
_M_savedId = _PMessage->msg_id();
|
|
async_send(NULL);
|
|
}
|
|
else
|
|
{
|
|
_Result = missed;
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accept the message by making a copy of the payload.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The runtime object identity of the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the message that the caller now has ownership of.
|
|
/// </returns>
|
|
/**/
|
|
virtual message<size_t> * accept_message(runtime_object_identity _MsgId)
|
|
{
|
|
// This check is to prevent spoofing and verify that the propagated message is
|
|
// the one that is accepted at the end.
|
|
if (_M_pSendMessage == NULL || _MsgId != _M_pSendMessage->msg_id())
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// If the source has disconnected then we can't allow for accept to succeed.
|
|
source_iterator _Iter = _M_connectedSources.begin();
|
|
ISource<_Type>* _PSource = *_Iter;
|
|
|
|
if (_PSource == NULL)
|
|
{
|
|
// source was disconnected. Fail accept.
|
|
return NULL;
|
|
}
|
|
|
|
_M_pReceiveMessage = _PSource->consume(_M_savedId, this);
|
|
|
|
_CONCRT_ASSERT(_M_pReceiveMessage != NULL);
|
|
|
|
//
|
|
// Instead of returning the internal message, we return a copy of the
|
|
// message passed in.
|
|
//
|
|
// Because we are returning a copy, the accept routine for a _Order_node
|
|
// does not need to grab the internal lock.
|
|
//
|
|
return (new message<size_t>(_M_pSendMessage->payload));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Takes the message and propagates it to all the targets of this _Order_node
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to a new message.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This function packages its _M_index into a message and immediately sends it to the targets.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void propagate_to_any_targets(_Inout_opt_ message<size_t> *)
|
|
{
|
|
if (_M_pSendMessage == NULL)
|
|
{
|
|
_Create_send_message();
|
|
}
|
|
|
|
for (target_iterator _Iter = _M_connectedTargets.begin(); *_Iter != NULL; ++_Iter)
|
|
{
|
|
ITarget<size_t> * _PTarget = *_Iter;
|
|
_Propagate_to_target(_PTarget);
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
/// <summary>
|
|
/// Propagate messages to the given target
|
|
/// </summary>
|
|
/**/
|
|
message_status _Propagate_to_target(ITarget<size_t> * _PTarget)
|
|
{
|
|
message_status _Status = _PTarget->propagate(_M_pSendMessage, this);
|
|
|
|
// If the message got rejected we have to release the hold on the source message.
|
|
if (_Status != accepted)
|
|
{
|
|
if (_M_savedId != -1)
|
|
{
|
|
// Release the reservation
|
|
source_iterator _Iter = _M_connectedSources.begin();
|
|
ISource<_Type> * _PSource = *_Iter;
|
|
|
|
if (_PSource != NULL)
|
|
{
|
|
_PSource->release(_M_savedId, this);
|
|
}
|
|
|
|
// If the source was disconnected, then it would
|
|
// automatically release any reservation. So we
|
|
// should reset our savedId regardless.
|
|
_M_savedId = -1;
|
|
}
|
|
|
|
}
|
|
|
|
return _Status;
|
|
}
|
|
|
|
//
|
|
// Private Data Members
|
|
//
|
|
|
|
// The source where we have reserved a message
|
|
ISource<_Type> * _M_pReservedSource;
|
|
|
|
// For greedy order-nodes, the message ID of subsequent messages sent to this node
|
|
// For non-greedy order nodes, the message ID of the message to reserve/consume
|
|
runtime_object_identity _M_savedId;
|
|
|
|
// The marker that indicates that _Reserving_node has reserved a message
|
|
volatile bool _M_fIsInitialized;
|
|
|
|
private:
|
|
//
|
|
// Hide assignment operator and copy constructor
|
|
//
|
|
_Reserving_node const & operator=(_Reserving_node const &); // no assignment operator
|
|
_Reserving_node(_Reserving_node const &); // no copy constructor
|
|
};
|
|
|
|
|
|
/// <summary>
|
|
/// Helper class used in multi-type greedy join blocks
|
|
/// Ordered node is a single-target, single-source ordered propagator block
|
|
/// </summary>
|
|
///
|
|
/// <typeparam name="_Type">
|
|
/// The payload type
|
|
/// </typeparam>
|
|
/**/
|
|
template<class _Type>
|
|
class _Greedy_node: public _Order_node_base<_Type>
|
|
{
|
|
public:
|
|
/// <summary>
|
|
/// Constructs a _Greedy_node within the default scheduler, and places it on any schedule
|
|
/// group of the scheduler's choosing.
|
|
/// </summary>
|
|
/// <param name="_PSource">
|
|
/// The source of data passed into the node
|
|
/// </param>
|
|
/// <param name="_Index">
|
|
/// The node's index, assigned from the outside.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the node will signal about having received its input data
|
|
/// </param>
|
|
/**/
|
|
_Greedy_node(ISource<_Type> * _PSource, size_t _Index, ITarget<size_t> * _PTarget = NULL) :
|
|
_M_savedId(-1),
|
|
_M_pGreedyMessage(NULL)
|
|
{
|
|
_Initialize_order_node(_PSource, _Index, _PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a _Greedy_node within the default scheduler, and places it on any schedule
|
|
/// group of the scheduler's choosing.
|
|
/// </summary>
|
|
/// <param name="_PSource">
|
|
/// The source of data passed into the node
|
|
/// </param>
|
|
/// <param name="_Index">
|
|
/// The node's index, assigned from the outside.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the node will signal about having received its input data
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A reference to a filter function.
|
|
/// </param>
|
|
/**/
|
|
_Greedy_node(ISource<_Type> * _PSource, size_t _Index, ITarget<size_t> * _PTarget, filter_method const& _Filter) :
|
|
_M_savedId(-1),
|
|
_M_pGreedyMessage(NULL)
|
|
{
|
|
register_filter(_Filter);
|
|
_Initialize_order_node(_PSource, _Index, _PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a _Greedy_node within the specified scheduler, and places it on any schedule
|
|
/// group of the scheduler's choosing.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// A reference to a scheduler instance.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// The source of data passed into the node
|
|
/// </param>
|
|
/// <param name="_Index">
|
|
/// The node's index, assigned from the outside.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the node will signal about having received its input data
|
|
/// </param>
|
|
/**/
|
|
_Greedy_node(Scheduler& _PScheduler, ISource<_Type> * _PSource, size_t _Index, ITarget<size_t> * _PTarget = NULL) :
|
|
_M_savedId(-1),
|
|
_M_pGreedyMessage(NULL)
|
|
{
|
|
_Initialize_order_node(_PSource, _Index, _PTarget, &_PScheduler);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a _Greedy_node within the specified scheduler, and places it on any schedule
|
|
/// group of the scheduler's choosing.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// A reference to a scheduler instance.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// The source of data passed into the node
|
|
/// </param>
|
|
/// <param name="_Index">
|
|
/// The node's index, assigned from the outside.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the node will signal about having received its input data
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A reference to a filter function.
|
|
/// </param>
|
|
/**/
|
|
_Greedy_node(Scheduler& _PScheduler, ISource<_Type> * _PSource, size_t _Index, ITarget<size_t> * _PTarget, filter_method const& _Filter) :
|
|
_M_savedId(-1),
|
|
_M_pGreedyMessage(NULL)
|
|
{
|
|
register_filter(_Filter);
|
|
_Initialize_order_node(_PSource, _Index, _PTarget, &_PScheduler);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a _Greedy_node within the specified schedule group. The scheduler is implied
|
|
/// by the schedule group.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// A reference to a schedule group.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// The source of data passed into the node
|
|
/// </param>
|
|
/// <param name="_Index">
|
|
/// The node's index, assigned from the outside.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the node will signal about having received its input data
|
|
/// </param>
|
|
/**/
|
|
_Greedy_node(ScheduleGroup& _PScheduleGroup, ISource<_Type> * _PSource, size_t _Index, ITarget<size_t> * _PTarget = NULL) :
|
|
_M_savedId(-1),
|
|
_M_pGreedyMessage(NULL)
|
|
{
|
|
_Initialize_order_node(_PSource, _Index, _PTarget, NULL, &_PScheduleGroup);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a _Greedy_node within the specified schedule group. The scheduler is implied
|
|
/// by the schedule group.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// A reference to a schedule group.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// The source of data passed into the node
|
|
/// </param>
|
|
/// <param name="_Index">
|
|
/// The node's index, assigned from the outside.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the node will signal about having received its input data
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A reference to a filter function.
|
|
/// </param>
|
|
/**/
|
|
_Greedy_node(ScheduleGroup& _PScheduleGroup, ISource<_Type> * _PSource, size_t _Index, ITarget<size_t> * _PTarget, filter_method const& _Filter) :
|
|
_M_savedId(-1),
|
|
_M_pGreedyMessage(NULL)
|
|
{
|
|
register_filter(_Filter);
|
|
_Initialize_order_node(_PSource, _Index, _PTarget, NULL, &_PScheduleGroup);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleans up any resources that may have been created by the _Greedy_node.
|
|
/// </summary>
|
|
/**/
|
|
~_Greedy_node()
|
|
{
|
|
// Remove all links
|
|
remove_network_links();
|
|
|
|
if (_M_pGreedyMessage != _M_pReceiveMessage)
|
|
{
|
|
delete _M_pGreedyMessage;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the _Greedy_node and prepares it for the next propagation
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// _Reset is called from Populate_destination_tuple through propagate_to_any_targets()
|
|
/// thus, it always has the internal lock held.
|
|
/// </remarks>
|
|
/**/
|
|
void _Reset()
|
|
{
|
|
_R_lock _Lock(_M_resetLock);
|
|
|
|
delete _M_pReceiveMessage;
|
|
_M_pReceiveMessage = NULL;
|
|
|
|
delete _M_pSendMessage;
|
|
_M_pSendMessage = NULL;
|
|
|
|
//
|
|
// For greedy type joins, look to see if any other messages have been
|
|
// passed to this _Greedy_node while the join was waiting for other
|
|
// messages to arrive. This function is already called with _M_resetLock
|
|
// held through propagate_to_any_targets().
|
|
//
|
|
for(;;)
|
|
{
|
|
// Set the current saved ID as -1. Check to see if something was ready for consumption
|
|
// (if _Saved_id != -1) and consume it if possible.
|
|
runtime_object_identity _Saved_id;
|
|
|
|
{
|
|
_NR_lock lockHolder(_M_propagationLock);
|
|
|
|
_Saved_id = _M_savedId;
|
|
|
|
if (_Saved_id == -1)
|
|
{
|
|
_M_pGreedyMessage = NULL;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
_M_savedId = -1;
|
|
}
|
|
}
|
|
|
|
if (_Saved_id != -1)
|
|
{
|
|
source_iterator _Iter = _M_connectedSources.begin();
|
|
|
|
ISource<_Type> * _PSource = *_Iter;
|
|
if ((_PSource != NULL) && _PSource->reserve(_Saved_id, this))
|
|
{
|
|
_M_pGreedyMessage = _PSource->consume(_Saved_id, this);
|
|
async_send(NULL);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected:
|
|
|
|
//
|
|
// propagator_block protected function implementation
|
|
//
|
|
|
|
/// <summary>
|
|
/// Asynchronously passes a message from an <c>ISource</c> block to this <c>ITarget</c> block. It is invoked
|
|
/// by the <c>propagate</c> method, when called by a source block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// It is important that calls to propagate do *not* take the same lock on the
|
|
/// internal structure that is used by Consume and the light-weight task. Doing so could
|
|
/// result in a deadlock with the Consume call.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message_status propagate_message(message<_Type> * _PMessage, ISource<_Type> * _PSource)
|
|
{
|
|
message_status _Result = postponed;
|
|
|
|
bool _FDone = false;
|
|
|
|
{
|
|
_NR_lock lockHolder(_M_propagationLock);
|
|
if (_M_pGreedyMessage != NULL)
|
|
{
|
|
_M_savedId = _PMessage->msg_id();
|
|
_Result = postponed;
|
|
_FDone = true;
|
|
}
|
|
}
|
|
|
|
if (!_FDone)
|
|
{
|
|
_M_pGreedyMessage = _PSource->accept(_PMessage->msg_id(), this);
|
|
|
|
if (_M_pGreedyMessage != NULL)
|
|
{
|
|
_Result = accepted;
|
|
async_send(NULL);
|
|
}
|
|
else
|
|
{
|
|
_Result = missed;
|
|
}
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accept the message by making a copy of the payload.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The runtime object identity of the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the message that the caller now has ownership of.
|
|
/// </returns>
|
|
/**/
|
|
virtual message<size_t> * accept_message(runtime_object_identity _MsgId)
|
|
{
|
|
// This check is to prevent spoofing and verify that the propagated message is
|
|
// the one that is accepted at the end.
|
|
if (_M_pSendMessage == NULL || _MsgId != _M_pSendMessage->msg_id())
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Instead of returning the internal message, we return a copy of the
|
|
// message passed in.
|
|
//
|
|
// Because we are returning a copy, the accept routine for a _Greedy_node
|
|
// does not need to grab the internal lock.
|
|
//
|
|
return (new message<size_t>(_M_pSendMessage->payload));
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Takes the message and propagates it to all the targets of this _Greedy_node
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to a new message.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This function packages its _M_index into a message and immediately sends it to the targets.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void propagate_to_any_targets(_Inout_opt_ message<size_t> *)
|
|
{
|
|
_R_lock _Lock(_M_resetLock);
|
|
|
|
if (_M_pSendMessage == NULL)
|
|
{
|
|
// Save the incoming message so that it can be consumed in the accept function
|
|
_M_pReceiveMessage = _M_pGreedyMessage;
|
|
_Create_send_message();
|
|
}
|
|
|
|
for (target_iterator _Iter = _M_connectedTargets.begin(); *_Iter != NULL; ++_Iter)
|
|
{
|
|
ITarget<size_t> * _PTarget = *_Iter;
|
|
_PTarget->propagate(_M_pSendMessage, this);
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
//
|
|
// Private Data Members
|
|
//
|
|
|
|
// The message to be saved by a greedy order node
|
|
message<_Type> * _M_pGreedyMessage;
|
|
|
|
// The lock used to protect propagation
|
|
::Concurrency::details::_NonReentrantPPLLock _M_propagationLock;
|
|
|
|
// The lock used to protect modification during a reset
|
|
::Concurrency::details::_ReentrantPPLLock _M_resetLock;
|
|
|
|
// For greedy order-nodes, the message ID of subsequent messages sent to this node
|
|
// For non-greedy order nodes, the message ID of the message to reserve/consume
|
|
runtime_object_identity _M_savedId;
|
|
|
|
private:
|
|
//
|
|
// Hide assignment operator and copy constructor
|
|
//
|
|
_Greedy_node const & operator=(_Greedy_node const &); // no assignment operator
|
|
_Greedy_node(_Greedy_node const &); // no copy constructor
|
|
};
|
|
|
|
|
|
/// <summary>
|
|
/// Helper class used in multi-type non-greedy join blocks
|
|
/// Ordered node is a single-target, single-source ordered propagator block
|
|
/// </summary>
|
|
///
|
|
/// <typeparam name="_Type">
|
|
/// The payload type
|
|
/// </typeparam>
|
|
/**/
|
|
template<class _Type>
|
|
class _Non_greedy_node: public _Order_node_base<_Type>
|
|
{
|
|
public:
|
|
/// <summary>
|
|
/// Constructs a _Non_greedy_node within the default scheduler, and places it on any schedule
|
|
/// group of the scheduler's choosing.
|
|
/// </summary>
|
|
/// <param name="_PSource">
|
|
/// The source of data passed into the node
|
|
/// </param>
|
|
/// <param name="_Index">
|
|
/// The node's index, assigned from the outside.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the node will signal about having received its input data
|
|
/// </param>
|
|
/**/
|
|
_Non_greedy_node(ISource<_Type> * _PSource, size_t _Index, ITarget<size_t> * _PTarget = NULL) :
|
|
_M_savedId(-1),
|
|
_M_reservedId(-1),
|
|
_M_pReservedSource(NULL)
|
|
{
|
|
_Initialize_order_node(_PSource, _Index, _PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a _Non_greedy_node within the default scheduler, and places it on any schedule
|
|
/// group of the scheduler's choosing.
|
|
/// </summary>
|
|
/// <param name="_PSource">
|
|
/// The source of data passed into the node
|
|
/// </param>
|
|
/// <param name="_Index">
|
|
/// The node's index, assigned from the outside.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the node will signal about having received its input data
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A reference to a filter function.
|
|
/// </param>
|
|
/**/
|
|
_Non_greedy_node(ISource<_Type> * _PSource, size_t _Index, ITarget<size_t> * _PTarget, filter_method const& _Filter) :
|
|
_M_savedId(-1),
|
|
_M_reservedId(-1),
|
|
_M_pReservedSource(NULL)
|
|
{
|
|
register_filter(_Filter);
|
|
_Initialize_order_node(_PSource, _Index, _PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a _Non_greedy_node within the specified scheduler, and places it on any schedule
|
|
/// group of the scheduler's choosing.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// A reference to a scheduler instance.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// The source of data passed into the node
|
|
/// </param>
|
|
/// <param name="_Index">
|
|
/// The node's index, assigned from the outside.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the node will signal about having received its input data
|
|
/// </param>
|
|
/**/
|
|
_Non_greedy_node(Scheduler& _PScheduler, ISource<_Type> * _PSource, size_t _Index, ITarget<size_t> * _PTarget = NULL) :
|
|
_M_savedId(-1),
|
|
_M_reservedId(-1),
|
|
_M_pReservedSource(NULL)
|
|
{
|
|
_Initialize_order_node(_PSource, _Index, _PTarget, &_PScheduler);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a _Non_greedy_node within the specified scheduler, and places it on any schedule
|
|
/// group of the scheduler's choosing.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// A reference to a scheduler instance.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// The source of data passed into the node
|
|
/// </param>
|
|
/// <param name="_Index">
|
|
/// The node's index, assigned from the outside.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the node will signal about having received its input data
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A reference to a filter function.
|
|
/// </param>
|
|
/**/
|
|
_Non_greedy_node(Scheduler& _PScheduler, ISource<_Type> * _PSource, size_t _Index, ITarget<size_t> * _PTarget, filter_method const& _Filter) :
|
|
_M_savedId(-1),
|
|
_M_reservedId(-1),
|
|
_M_pReservedSource(NULL)
|
|
{
|
|
register_filter(_Filter);
|
|
_Initialize_order_node(_PSource, _Index, _PTarget, &_PScheduler);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a _Non_greedy_node within the specified schedule group. The scheduler is implied
|
|
/// by the schedule group.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// A reference to a schedule group.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// The source of data passed into the node
|
|
/// </param>
|
|
/// <param name="_Index">
|
|
/// The node's index, assigned from the outside.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the node will signal about having received its input data
|
|
/// </param>
|
|
/**/
|
|
_Non_greedy_node(ScheduleGroup& _PScheduleGroup, ISource<_Type> * _PSource, size_t _Index, ITarget<size_t> * _PTarget = NULL) :
|
|
_M_savedId(-1),
|
|
_M_reservedId(-1),
|
|
_M_pReservedSource(NULL)
|
|
{
|
|
_Initialize_order_node(_PSource, _Index, _PTarget, NULL, &_PScheduleGroup);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a _Non_greedy_node within the specified schedule group. The scheduler is implied
|
|
/// by the schedule group.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// A reference to a schedule group.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// The source of data passed into the node
|
|
/// </param>
|
|
/// <param name="_Index">
|
|
/// The node's index, assigned from the outside.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// The target to which the node will signal about having received its input data
|
|
/// </param>
|
|
/// <param name="_Filter">
|
|
/// A reference to a filter function.
|
|
/// </param>
|
|
/**/
|
|
_Non_greedy_node(ScheduleGroup& _PScheduleGroup, ISource<_Type> * _PSource, size_t _Index, ITarget<size_t> * _PTarget, filter_method const& _Filter) :
|
|
_M_savedId(-1),
|
|
_M_reservedId(-1),
|
|
_M_pReservedSource(NULL)
|
|
{
|
|
register_filter(_Filter);
|
|
_Initialize_order_node(_PSource, _Index, _PTarget, NULL, &_PScheduleGroup);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleans up any resources that may have been created by the _Order_node.
|
|
/// </summary>
|
|
/**/
|
|
~_Non_greedy_node()
|
|
{
|
|
if (_M_pReservedSource != NULL)
|
|
{
|
|
_M_pReservedSource = NULL;
|
|
_M_connectedSources.release();
|
|
}
|
|
|
|
// Remove all links
|
|
remove_network_links();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the _Order_node and prepares it for the next propagation
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// _Reset is called from Populate_destination_tuple through propagate_to_any_targets()
|
|
/// thus, it always has the internal lock held.
|
|
/// </remarks>
|
|
/**/
|
|
void _Reset()
|
|
{
|
|
_R_lock _Lock(_M_resetLock);
|
|
|
|
delete _M_pReceiveMessage;
|
|
_M_pReceiveMessage = NULL;
|
|
|
|
delete _M_pSendMessage;
|
|
_M_pSendMessage = NULL;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called for a non_greedy type join block in order to reserve the message
|
|
/// in this join block
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A bool indicating whether the reservation worked
|
|
/// </returns>
|
|
/**/
|
|
bool _Reserve_received_message()
|
|
{
|
|
bool _Ret_val = false;
|
|
|
|
// Order node has only a single source.
|
|
// Obtain an iterator to the first source. It will guarantee that the reference
|
|
// count on the source is maintained
|
|
source_iterator _Iter = _M_connectedSources.begin();
|
|
ISource<_Type> * _PSource = *_Iter;
|
|
|
|
if (_PSource != NULL)
|
|
{
|
|
// CAS out the current saved ID, in order to try and reserve it
|
|
runtime_object_identity _SavedId = _InterlockedExchange((volatile long *) &_M_savedId, -1);
|
|
|
|
_Ret_val = _PSource->reserve(_SavedId, this);
|
|
//
|
|
// If this reserved failed, that means we need to wait for another message
|
|
// to come in on this link. _M_savedID was set to -1 to indicate to the _Order_node
|
|
// that it needs to async_send when that next message comes through
|
|
//
|
|
// If the reserve succeeds, save away the reserved ID. This will be use later in
|
|
// consume
|
|
//
|
|
if (_Ret_val)
|
|
{
|
|
_M_reservedId = _SavedId;
|
|
|
|
// Acquire a reference on the source
|
|
_M_connectedSources.reference();
|
|
_M_pReservedSource = _PSource;
|
|
}
|
|
}
|
|
|
|
return _Ret_val;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called for a non_greedy type join block in order to consume the message
|
|
/// in this join block that has been reserved
|
|
/// </summary>
|
|
/**/
|
|
void _Consume_received_message()
|
|
{
|
|
if (_M_pReservedSource != NULL)
|
|
{
|
|
runtime_object_identity _SavedId = _M_reservedId;
|
|
_M_pReceiveMessage = _M_pReservedSource->consume(_SavedId, this);
|
|
|
|
runtime_object_identity _OldId = NULL;
|
|
_OldId = _InterlockedExchange((volatile long *) &_M_reservedId, -1);
|
|
|
|
_CONCRT_ASSERT(_OldId == _SavedId);
|
|
|
|
// Release the reference on the source
|
|
_M_pReservedSource = NULL;
|
|
_M_connectedSources.release();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called for a non_greedy type join block release a reservation on this block
|
|
/// </summary>
|
|
/**/
|
|
bool _Release_received_message()
|
|
{
|
|
bool retVal = false;
|
|
|
|
if (_M_pReservedSource != NULL)
|
|
{
|
|
runtime_object_identity _SavedId = _M_reservedId;
|
|
// If the _M_savedId is still -1, then swap the succeeded one back
|
|
_M_pReservedSource->release(_SavedId, this);
|
|
|
|
if (_InterlockedCompareExchange((volatile long *) &_M_savedId, _SavedId, -1) == -1)
|
|
{
|
|
retVal = true;
|
|
}
|
|
|
|
// Release the reference on the source
|
|
_M_pReservedSource = NULL;
|
|
_M_connectedSources.release();
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
protected:
|
|
|
|
//
|
|
// propagator_block protected function implementation
|
|
//
|
|
|
|
/// <summary>
|
|
/// Asynchronously passes a message from an <c>ISource</c> block to this <c>ITarget</c> block. It is invoked
|
|
/// by the <c>propagate</c> method, when called by a source block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// It is important that calls to propagate do *not* take the same lock on the
|
|
/// internal structure that is used by Consume and the light-weight task. Doing so could
|
|
/// result in a deadlock with the Consume call.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message_status propagate_message(message<_Type> * _PMessage, ISource<_Type> *)
|
|
{
|
|
// Change the message ID. If it was -1, that means an async-send needs to occur
|
|
if (_InterlockedExchange((volatile long *) &_M_savedId, _PMessage->msg_id()) == -1)
|
|
{
|
|
async_send(NULL);
|
|
}
|
|
|
|
// Always return postponed. This message will be consumed
|
|
// in the LWT
|
|
|
|
return postponed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accept the message by making a copy of the payload.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The runtime object identity of the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the message that the caller now has ownership of.
|
|
/// </returns>
|
|
/**/
|
|
virtual message<size_t> * accept_message(runtime_object_identity _MsgId)
|
|
{
|
|
// This check is to prevent spoofing and verify that the propagated message is
|
|
// the one that is accepted at the end.
|
|
if (_M_pSendMessage == NULL || _MsgId != _M_pSendMessage->msg_id())
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Instead of returning the internal message, we return a copy of the
|
|
// message passed in.
|
|
//
|
|
// Because we are returning a copy, the accept routine for a _Non_greedy_node
|
|
// does not need to grab the internal lock.
|
|
//
|
|
return (new message<size_t>(_M_pSendMessage->payload));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Takes the message and propagates it to all the targets of this _Order_node
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to a new message.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This function packages its _M_index into a message and immediately sends it to the targets.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void propagate_to_any_targets(_Inout_opt_ message<size_t> *)
|
|
{
|
|
_R_lock _Lock(_M_resetLock);
|
|
|
|
if (_M_pSendMessage == NULL)
|
|
{
|
|
_Create_send_message();
|
|
}
|
|
|
|
for (target_iterator _Iter = _M_connectedTargets.begin(); *_Iter != NULL; ++_Iter)
|
|
{
|
|
ITarget<size_t> * _PTarget = *_Iter;
|
|
_PTarget->propagate(_M_pSendMessage, this);
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
//
|
|
// Private Data Members
|
|
//
|
|
|
|
// The source where we have reserved a message
|
|
ISource<_Type> * _M_pReservedSource;
|
|
|
|
// The lock used to protect modification during a reset
|
|
::Concurrency::details::_ReentrantPPLLock _M_resetLock;
|
|
|
|
// For non-greedy order nodes, the message ID of the message to reserve/consume
|
|
runtime_object_identity _M_savedId;
|
|
|
|
// For non-greedy order nodes, the reserved ID of the message that was reserved
|
|
runtime_object_identity _M_reservedId;
|
|
|
|
// The marker that indicates that _Non_greedy_node has reserved a message
|
|
volatile bool _M_fIsInitialized;
|
|
|
|
private:
|
|
//
|
|
// Hide assignment operator and copy constructor
|
|
//
|
|
_Non_greedy_node const & operator=(_Non_greedy_node const &); // no assignment operator
|
|
_Non_greedy_node(_Non_greedy_node const &); // no copy constructor
|
|
};
|
|
|
|
//**************************************************************************
|
|
// Choice:
|
|
//**************************************************************************
|
|
|
|
/// <summary>
|
|
/// A <c>choice</c> messaging block is a multi-source, single-target block that represents a control-flow
|
|
/// interaction with a set of sources. The choice block will wait for any one of multiple sources to
|
|
/// produce a message and will propagate the index of the source that produced the message.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// A <c>tuple</c>-based type representing the payloads of the input sources.
|
|
/// </typeparam>
|
|
/// <remarks>
|
|
/// The choice block ensures that only one of the incoming messages is consumed.
|
|
/// <para>For more information, see <see cref="Asynchronous Message Blocks"/>.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="join Class"/>
|
|
/// <seealso cref="single_assignment Class"/>
|
|
/// <seealso cref="make_choice Function"/>
|
|
/// <seealso cref="tuple Class"/>
|
|
/**/
|
|
template<class _Type>
|
|
class choice: public ISource<size_t>
|
|
{
|
|
public:
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>choice</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_Tuple">
|
|
/// A <c>tuple</c> of sources for the choice.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// </para>
|
|
/// <para>
|
|
/// Move construction is not performed under a lock, which means that it is up to the user
|
|
/// to make sure that there are no light-weight tasks in flight at the time of moving.
|
|
/// Otherwise, numerous races can occur, leading to exceptions or inconsistent state.
|
|
/// </para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
explicit choice(_Type _Tuple) : _M_sourceTuple(_Tuple), _M_pScheduler(NULL), _M_pScheduleGroup(NULL)
|
|
{
|
|
_M_pSingleAssignment = new single_assignment<size_t>();
|
|
_Initialize_choices<0>();
|
|
}
|
|
|
|
#ifdef _CRT_USE_WINAPI_FAMILY_DESKTOP_APP
|
|
/// <summary>
|
|
/// Constructs a <c>choice</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// The <c>Scheduler</c> object within which the propagation task for the <c>choice</c> messaging block is scheduled.
|
|
/// </param>
|
|
/// <param name="_Tuple">
|
|
/// A <c>tuple</c> of sources for the choice.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// </para>
|
|
/// <para>
|
|
/// Move construction is not performed under a lock, which means that it is up to the user
|
|
/// to make sure that there are no light-weight tasks in flight at the time of moving.
|
|
/// Otherwise, numerous races can occur, leading to exceptions or inconsistent state.
|
|
/// </para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
choice(Scheduler& _PScheduler, _Type _Tuple) : _M_sourceTuple(_Tuple), _M_pScheduler(&_PScheduler), _M_pScheduleGroup(NULL)
|
|
{
|
|
_M_pSingleAssignment = new single_assignment<size_t>(_PScheduler);
|
|
_Initialize_choices<0>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>choice</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The <c>ScheduleGroup</c> object within which the propagation task for the <c>choice</c> messaging block is scheduled.
|
|
/// The <c>Scheduler</c> object used is implied by the schedule group.
|
|
/// </param>
|
|
/// <param name="_Tuple">
|
|
/// A <c>tuple</c> of sources for the choice.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// </para>
|
|
/// <para>
|
|
/// Move construction is not performed under a lock, which means that it is up to the user
|
|
/// to make sure that there are no light-weight tasks in flight at the time of moving.
|
|
/// Otherwise, numerous races can occur, leading to exceptions or inconsistent state.
|
|
/// </para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
choice(ScheduleGroup& _PScheduleGroup, _Type _Tuple) : _M_sourceTuple(_Tuple), _M_pScheduler(NULL), _M_pScheduleGroup(&_PScheduleGroup)
|
|
{
|
|
_M_pSingleAssignment = new single_assignment<size_t>(_PScheduleGroup);
|
|
_Initialize_choices<0>();
|
|
}
|
|
#endif /* _CRT_USE_WINAPI_FAMILY_DESKTOP_APP */
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>choice</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_Choice">
|
|
/// A <c>choice</c> messaging block to copy from.
|
|
/// Note that the original object is orphaned, making this a move constructor.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// </para>
|
|
/// <para>
|
|
/// Move construction is not performed under a lock, which means that it is up to the user
|
|
/// to make sure that there are no light-weight tasks in flight at the time of moving.
|
|
/// Otherwise, numerous races can occur, leading to exceptions or inconsistent state.
|
|
/// </para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
choice(choice && _Choice)
|
|
{
|
|
// Copy scheduler group or scheduler to the new object.
|
|
_M_pScheduleGroup = _Choice._M_pScheduleGroup;
|
|
_M_pScheduler = _Choice._M_pScheduler;
|
|
|
|
// Single assignment is heap allocated, so simply copy the pointer. If it already has
|
|
// a value, it will be preserved.
|
|
_M_pSingleAssignment = _Choice._M_pSingleAssignment;
|
|
_Choice._M_pSingleAssignment = NULL;
|
|
|
|
// Invoke copy assignment for tuple to copy pointers to message blocks.
|
|
_M_sourceTuple = _Choice._M_sourceTuple;
|
|
|
|
// Copy the pointers to order nodes to a new object and zero out in the old object.
|
|
memcpy(_M_pSourceChoices, _Choice._M_pSourceChoices, sizeof(_M_pSourceChoices));
|
|
memset(_Choice._M_pSourceChoices, 0, sizeof(_M_pSourceChoices));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Destroys the <c>choice</c> messaging block.
|
|
/// </summary>
|
|
/**/
|
|
~choice()
|
|
{
|
|
delete _M_pSingleAssignment;
|
|
_Delete_choices<0>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// A type alias for <typeparamref name="_Type"/>.
|
|
/// </summary>
|
|
/**/
|
|
typedef typename _Type type;
|
|
|
|
/// <summary>
|
|
/// Checks whether this <c>choice</c> messaging block has been initialized with a value yet.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// <c>true</c> if the block has received a value, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/**/
|
|
bool has_value() const
|
|
{
|
|
return _M_pSingleAssignment->has_value();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an index into the <c>tuple</c> representing the element selected by the
|
|
/// <c>choice</c> messaging block.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The message index.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// The message payload can be extracted using the <c>get</c> method.
|
|
/// </remarks>
|
|
/**/
|
|
size_t index()
|
|
{
|
|
return _M_pSingleAssignment->value();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the message whose index was selected by the <c>choice</c> messaging block.
|
|
/// </summary>
|
|
/// <typeparam name="_Payload_type">
|
|
/// The type of the message payload.
|
|
/// </typeparam>
|
|
/// <returns>
|
|
/// The payload of the message.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// Because a <c>choice</c> messaging block can take inputs with different payload types, you must specify
|
|
/// the type of the payload at the point of retrieval. You can determine the type based on the result of
|
|
/// the <c>index</c> method.
|
|
/// </remarks>
|
|
/**/
|
|
template <typename _Payload_type>
|
|
_Payload_type const & value()
|
|
{
|
|
return reinterpret_cast<_Reserving_node<_Payload_type> *>(_M_pSourceChoices[_M_pSingleAssignment->value()])->value();
|
|
}
|
|
|
|
//
|
|
// ISource public function implementations
|
|
//
|
|
|
|
/// <summary>
|
|
/// Links a target block to this <c>choice</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to an <c>ITarget</c> block to link to this <c>choice</c> messaging block.
|
|
/// </param>
|
|
/**/
|
|
virtual void link_target(_Inout_ ITarget<size_t> * _PTarget)
|
|
{
|
|
_M_pSingleAssignment->link_target(_PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unlinks a target block from this <c>choice</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to an <c>ITarget</c> block to unlink from this <c>choice</c> messaging block.
|
|
/// </param>
|
|
/**/
|
|
virtual void unlink_target(_Inout_ ITarget<size_t> * _PTarget)
|
|
{
|
|
_M_pSingleAssignment->unlink_target(_PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unlinks all targets from this <c>choice</c> messaging block.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This method does not need to be called from the destructor because destructor for the internal
|
|
/// <c>single_assignment</c> block will unlink properly.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void unlink_targets()
|
|
{
|
|
_M_pSingleAssignment->unlink_targets();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accepts a message that was offered by this <c>choice</c> block, transferring ownership to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the offered <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling the <c>accept</c> method.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the message that the caller now has ownership of.
|
|
/// </returns>
|
|
/**/
|
|
virtual message<size_t> * accept(runtime_object_identity _MsgId, _Inout_ ITarget<size_t> * _PTarget)
|
|
{
|
|
return _M_pSingleAssignment->accept(_MsgId, _PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reserves a message previously offered by this <c>choice</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being reserved.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling the <c>reserve</c> method.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the message was successfully reserved, <c>false</c> otherwise. Reservations can fail
|
|
/// for many reasons, including: the message was already reserved or accepted by another target, the source could
|
|
/// deny reservations, and so forth.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// After you call <c>reserve</c>, if it succeeds, you must call either <c>consume</c> or <c>release</c>
|
|
/// in order to take or give up possession of the message, respectively.
|
|
/// </remarks>
|
|
/**/
|
|
virtual bool reserve(runtime_object_identity _MsgId, _Inout_ ITarget<size_t> * _PTarget)
|
|
{
|
|
return _M_pSingleAssignment->reserve(_MsgId, _PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Consumes a message previously offered by this <c>choice</c> messaging block and successfully reserved by the target,
|
|
/// transferring ownership to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the reserved <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling the <c>consume</c> method.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the <c>message</c> object that the caller now has ownership of.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// The <c>consume</c> method is similar to <c>accept</c>, but must always be preceded by a call to <c>reserve</c> that
|
|
/// returned <c>true</c>.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message<size_t> * consume(runtime_object_identity _MsgId, _Inout_ ITarget<size_t> * _PTarget)
|
|
{
|
|
return _M_pSingleAssignment->consume(_MsgId, _PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases a previous successful message reservation.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being released.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling the <c>release</c> method.
|
|
/// </param>
|
|
/**/
|
|
virtual void release(runtime_object_identity _MsgId, _Inout_ ITarget<size_t> * _PTarget)
|
|
{
|
|
_M_pSingleAssignment->release(_MsgId, _PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Acquires a reference count on this <c>choice</c> messaging block, to prevent deletion.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling this method.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This method is called by an <c>ITarget</c> object that is being linked to this source
|
|
/// during the <c>link_target</c> method.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void acquire_ref(_Inout_ ITarget<size_t> * _PTarget)
|
|
{
|
|
_M_pSingleAssignment->acquire_ref(_PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases a reference count on this <c>choice</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling this method.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This method is called by an <c>ITarget</c> object that is being unlinked from this source.
|
|
/// The source block is allowed to release any resources reserved for the target block.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void release_ref(_Inout_ ITarget<size_t> * _PTarget)
|
|
{
|
|
_M_pSingleAssignment->release_ref(_PTarget);
|
|
}
|
|
|
|
private:
|
|
|
|
/// <summary>
|
|
/// Constructs and initializes a _Reserving_node for each tuple messaging block passed in.
|
|
/// </summary>
|
|
/// <typeparam>The highest-number index of the choice's sources</typeparam>
|
|
/**/
|
|
template<int _Index>
|
|
void _Initialize_choices()
|
|
{
|
|
std::tr1::tuple_element<_Index, _Type>::type _Item = std::tr1::get<_Index>(_M_sourceTuple);
|
|
_Reserving_node<std::tr1::remove_pointer<std::tr1::tuple_element<_Index, _Type>::type>::type::source_type> * _Order_node_element = NULL;
|
|
|
|
if (_M_pScheduleGroup != NULL)
|
|
{
|
|
_Order_node_element = new _Reserving_node<std::tr1::remove_pointer<std::tr1::tuple_element<_Index, _Type>::type>::type::source_type> (*_M_pScheduleGroup, _Item, _Index);
|
|
}
|
|
else if (_M_pScheduler != NULL)
|
|
{
|
|
_Order_node_element = new _Reserving_node<std::tr1::remove_pointer<std::tr1::tuple_element<_Index, _Type>::type>::type::source_type> (*_M_pScheduler, _Item, _Index);
|
|
}
|
|
else
|
|
{
|
|
_Order_node_element = new _Reserving_node<std::tr1::remove_pointer<std::tr1::tuple_element<_Index, _Type>::type>::type::source_type> (_Item, _Index);
|
|
}
|
|
|
|
_M_pSourceChoices[_Index] = _Order_node_element;
|
|
_Order_node_element->link_target(_M_pSingleAssignment);
|
|
_Initialize_choices<_Index + 1>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides a sentinel template specialization for _Initialize_choices recursive
|
|
/// template expansion.
|
|
/// </summary>
|
|
/**/
|
|
template<> void _Initialize_choices<std::tr1::tuple_size<_Type>::value>()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes all _Reserving_node elements that were created in _Initialize_choices.
|
|
/// </summary>
|
|
/// <typeparam>The highest-number index of the choice's sources</typeparam>
|
|
/**/
|
|
template<int _Index>
|
|
void _Delete_choices()
|
|
{
|
|
delete reinterpret_cast<_Reserving_node<std::tr1::remove_pointer<std::tr1::tuple_element<_Index, _Type>::type>::type::source_type> *>(_M_pSourceChoices[_Index]);
|
|
_M_pSourceChoices[_Index] = NULL;
|
|
_Delete_choices<_Index + 1>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides a sentinel template specialization for _Delete_choices recursive
|
|
/// template expansion.
|
|
/// </summary>
|
|
/**/
|
|
template<> void _Delete_choices<std::tr1::tuple_size<_Type>::value>()
|
|
{
|
|
}
|
|
|
|
// Array of pointers to _Reserving_node elements representing each source
|
|
void * _M_pSourceChoices[std::tr1::tuple_size<_Type>::value];
|
|
|
|
// Single assignment which chooses between source messaging blocks
|
|
single_assignment<size_t> * _M_pSingleAssignment;
|
|
|
|
// Tuple of messaging blocks that are sources to this choice
|
|
_Type _M_sourceTuple;
|
|
|
|
// The scheduler to propagate messages on
|
|
Scheduler * _M_pScheduler;
|
|
|
|
// The schedule group to propagate messages on
|
|
ScheduleGroup * _M_pScheduleGroup;
|
|
|
|
private:
|
|
//
|
|
// Hide assignment operator
|
|
//
|
|
choice const &operator =(choice const &); // no assignment operator
|
|
choice(choice const &); // no copy constructor
|
|
};
|
|
|
|
// Templated factory functions that create a choice, three flavors
|
|
|
|
#ifdef _CRT_USE_WINAPI_FAMILY_DESKTOP_APP
|
|
/// <summary>
|
|
/// Constructs a <c>choice</c> messaging block from an optional <c>Scheduler</c> or <c>ScheduleGroup</c>
|
|
/// and two or more input sources.
|
|
/// </summary>
|
|
/// <typeparam name="_Type1">
|
|
/// The message block type of the first source.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Type2">
|
|
/// The message block type of the second source.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Types">
|
|
/// The message block types of additional sources.
|
|
/// </typeparam>
|
|
/// <param name="_PScheduler">
|
|
/// The <c>Scheduler</c> object within which the propagation task for the <c>choice</c> messaging block is scheduled.
|
|
/// </param>
|
|
/// <param name="_Item1">
|
|
/// The first source.
|
|
/// </param>
|
|
/// <param name="_Item2">
|
|
/// The second source.
|
|
/// </param>
|
|
/// <param name="_Items">
|
|
/// Additional sources.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <c>choice</c> message block with two or more input sources.
|
|
/// </returns>
|
|
/// <seealso cref="choice Class"/>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/**/
|
|
template<typename _Type1, typename _Type2, typename... _Types>
|
|
choice<std::tuple<_Type1, _Type2, _Types...>>
|
|
make_choice(Scheduler& _PScheduler, _Type1 _Item1, _Type2 _Item2, _Types... _Items)
|
|
{
|
|
return choice<std::tuple<_Type1, _Type2, _Types...>>(_PScheduler, std::make_tuple(_Item1, _Item2, _Items...));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>choice</c> messaging block from an optional <c>Scheduler</c> or <c>ScheduleGroup</c>
|
|
/// and two or more input sources.
|
|
/// </summary>
|
|
/// <typeparam name="_Type1">
|
|
/// The message block type of the first source.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Type2">
|
|
/// The message block type of the second source.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Types">
|
|
/// The message block types of additional sources.
|
|
/// </typeparam>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The <c>ScheduleGroup</c> object within which the propagation task for the <c>choice</c> messaging block is scheduled.
|
|
/// The <c>Scheduler</c> object used is implied by the schedule group.
|
|
/// </param>
|
|
/// <param name="_Item1">
|
|
/// The first source.
|
|
/// </param>
|
|
/// <param name="_Item2">
|
|
/// The second source.
|
|
/// </param>
|
|
/// <param name="_Items">
|
|
/// Additional sources.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <c>choice</c> message block with two or more input sources.
|
|
/// </returns>
|
|
/// <seealso cref="choice Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
template<typename _Type1, typename _Type2, typename... _Types>
|
|
choice<std::tuple<_Type1, _Type2, _Types...>>
|
|
make_choice(ScheduleGroup& _PScheduleGroup, _Type1 _Item1, _Type2 _Item2, _Types... _Items)
|
|
{
|
|
return choice<std::tuple<_Type1, _Type2, _Types...>>(_PScheduleGroup, std::make_tuple(_Item1, _Item2, _Items...));
|
|
}
|
|
|
|
#endif /* _CRT_USE_WINAPI_FAMILY_DESKTOP_APP */
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>choice</c> messaging block from an optional <c>Scheduler</c> or <c>ScheduleGroup</c>
|
|
/// and two or more input sources.
|
|
/// </summary>
|
|
/// <typeparam name="_Type1">
|
|
/// The message block type of the first source.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Type2">
|
|
/// The message block type of the second source.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Types">
|
|
/// The message block types of additional sources.
|
|
/// </typeparam>
|
|
/// <param name="_Item1">
|
|
/// The first source.
|
|
/// </param>
|
|
/// <param name="_Item2">
|
|
/// The second source.
|
|
/// </param>
|
|
/// <param name="_Items">
|
|
/// Additional sources.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <c>choice</c> message block with two or more input sources.
|
|
/// </returns>
|
|
/// <seealso cref="choice Class"/>
|
|
/**/
|
|
template<typename _Type1, typename _Type2, typename... _Types>
|
|
choice<std::tuple<_Type1, _Type2, _Types...>>
|
|
make_choice(_Type1 _Item1, _Type2 _Item2, _Types... _Items)
|
|
{
|
|
return choice<std::tuple<_Type1, _Type2, _Types...>>(std::make_tuple(_Item1, _Item2, _Items...));
|
|
}
|
|
|
|
//**************************************************************************
|
|
// Join:
|
|
//**************************************************************************
|
|
|
|
// Template specialization used to unwrap the types from within a tuple.
|
|
|
|
/**/
|
|
template <typename _Tuple> struct _Unwrap;
|
|
|
|
/// <summary>
|
|
/// Template specialization used to unwrap the types from within a tuple.
|
|
/// </summary>
|
|
/// <typeparam name="_Types">
|
|
/// The types of the elements of the tuple.
|
|
/// </typeparam>
|
|
/**/
|
|
template <typename... _Types>
|
|
struct _Unwrap<std::tuple<_Types...>>
|
|
{
|
|
typedef std::tuple<typename std::remove_pointer<_Types>::type::source_type...> type;
|
|
};
|
|
|
|
/// <summary>
|
|
/// Defines a block allowing sources of distinct types to be joined.
|
|
/// Join node is a single-target, multi-source ordered propagator block
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload tuple type
|
|
/// </typeparam>
|
|
/// <typeparam name="_Jtype">
|
|
/// The kind of join this is, either 'greedy' or 'non-greedy'
|
|
/// </typeparam>
|
|
/**/
|
|
template<typename _Type, typename _Destination_type, join_type _Jtype>
|
|
class _Join_node: public propagator_block<single_link_registry<ITarget<_Destination_type>>, multi_link_registry<ISource<size_t>>>
|
|
{
|
|
public:
|
|
|
|
/// <summary>
|
|
/// Constructs a join within the default scheduler, and places it on any schedule
|
|
/// group of the scheduler's choosing.
|
|
/// </summary>
|
|
/**/
|
|
_Join_node() : _M_counter(std::tr1::tuple_size<_Destination_type>::value)
|
|
{
|
|
initialize_source_and_target();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a join within the specified scheduler, and places it on any schedule
|
|
/// group of the scheduler's choosing.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// A reference to a scheduler instance.
|
|
/// </param>
|
|
/**/
|
|
_Join_node(Scheduler& _PScheduler) : _M_counter(std::tr1::tuple_size<_Destination_type>::value)
|
|
{
|
|
initialize_source_and_target(&_PScheduler);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a join within the specified schedule group. The scheduler is implied
|
|
/// by the schedule group.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// A reference to a schedule group.
|
|
/// </param>
|
|
/**/
|
|
_Join_node(ScheduleGroup& _PScheduleGroup) : _M_counter(std::tr1::tuple_size<_Destination_type>::value)
|
|
{
|
|
initialize_source_and_target(NULL, &_PScheduleGroup);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleans up any resources that may have been created by the join.
|
|
/// </summary>
|
|
/**/
|
|
~_Join_node()
|
|
{
|
|
// Remove all links
|
|
remove_network_links();
|
|
|
|
// Clean up any messages left in this message block
|
|
_Delete_stored_messages();
|
|
}
|
|
|
|
protected:
|
|
|
|
/// <summary>
|
|
/// Asynchronously passes a message from an <c>ISource</c> block to this <c>ITarget</c> block. It is invoked
|
|
/// by the <c>propagate</c> method, when called by a source block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to the <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PSource">
|
|
/// A pointer to the source block offering the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="message_status Enumeration">message_status</see> indication of what
|
|
/// the target decided to do with the message.
|
|
/// </returns>
|
|
/**/
|
|
virtual message_status propagate_message(message<size_t> * _PMessage, ISource<size_t> * _PSource)
|
|
{
|
|
// This join block is connected to the _Order_node sources, which know not to send
|
|
// any more messages until join propagates them further. That is why join can
|
|
// always accept the incoming messages.
|
|
|
|
_PMessage = _PSource->accept(_PMessage->msg_id(), this);
|
|
|
|
//
|
|
// Source block created an int message only to notify join that the real
|
|
// payload is available. There is no need to keep this message around.
|
|
//
|
|
_CONCRT_ASSERT(_PMessage != NULL);
|
|
delete _PMessage;
|
|
|
|
long _Ret_val = _InterlockedDecrement(&_M_counter);
|
|
|
|
_CONCRT_ASSERT(_Ret_val >= 0);
|
|
|
|
if (_Ret_val == 0)
|
|
{
|
|
//
|
|
// All source messages are now received so join can propagate them further
|
|
//
|
|
async_send(NULL);
|
|
}
|
|
|
|
return accepted;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accepts an offered message by the source, transferring ownership to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The runtime object identity of the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the message that the caller now has ownership of.
|
|
/// </returns>
|
|
/**/
|
|
virtual message<_Destination_type> * accept_message(runtime_object_identity _MsgId)
|
|
{
|
|
//
|
|
// Peek at the head message in the message buffer. If the IDs match
|
|
// dequeue and transfer ownership
|
|
//
|
|
message<_Destination_type> * _Msg = NULL;
|
|
|
|
if (_M_messageBuffer._Is_head(_MsgId))
|
|
{
|
|
_Msg = _M_messageBuffer._Dequeue();
|
|
}
|
|
|
|
return _Msg;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reserves a message previously offered by the source.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The runtime object identity of the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A bool indicating whether the reservation worked or not.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// After <c>reserve</c> is called, if it returns <c>true</c>, either <c>consume</c> or <c>release</c> must be called
|
|
/// to either take or release ownership of the message.
|
|
/// </remarks>
|
|
/**/
|
|
virtual bool reserve_message(runtime_object_identity _MsgId)
|
|
{
|
|
// Allow reservation if this is the head message
|
|
return _M_messageBuffer._Is_head(_MsgId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Consumes a message previously offered by the source and reserved by the target,
|
|
/// transferring ownership to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The runtime object identity of the message.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the message that the caller now has ownership of.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// <c>consume_message</c> is similar to <c>accept</c>, but is always preceded by a call to <c>reserve</c>.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message<_Destination_type> * consume_message(runtime_object_identity _MsgId)
|
|
{
|
|
// By default, accept the message
|
|
return accept_message(_MsgId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases a previous message reservation.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The runtime object identity of the message.
|
|
/// </param>
|
|
/**/
|
|
virtual void release_message(runtime_object_identity _MsgId)
|
|
{
|
|
// The head message is the one reserved.
|
|
if (!_M_messageBuffer._Is_head(_MsgId))
|
|
{
|
|
throw message_not_found();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resumes propagation after a reservation has been released
|
|
/// </summary>
|
|
/**/
|
|
virtual void resume_propagation()
|
|
{
|
|
// If there are any messages in the buffer, propagate them out
|
|
if (_M_messageBuffer._Count() > 0)
|
|
{
|
|
async_send(NULL);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Notification that a target was linked to this source.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the newly linked target.
|
|
/// </param>
|
|
/**/
|
|
virtual void link_target_notification(_Inout_ ITarget<_Destination_type> *)
|
|
{
|
|
// There is only a single target.
|
|
_Propagate_priority_order(_M_messageBuffer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Takes the message and propagates it to all the targets of this <c>join</c> block.
|
|
/// </summary>
|
|
/// <param name="_PMessage">
|
|
/// A pointer to a new message.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This function packages source payloads into a tuple message and immediately sends it to the targets.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void propagate_to_any_targets(_Inout_opt_ message<_Destination_type> *)
|
|
{
|
|
message<_Destination_type> * _Msg = NULL;
|
|
|
|
if (_M_counter == 0)
|
|
{
|
|
bool fIsNonGreedy = (_Jtype == non_greedy);
|
|
|
|
if (fIsNonGreedy)
|
|
{
|
|
if (!_Non_greedy_acquire_messages())
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!fIsNonGreedy)
|
|
{
|
|
// Because a greedy join has captured all input, we can reset
|
|
// the counter to the total number of inputs
|
|
_InterlockedExchange(&_M_counter, std::tr1::tuple_size<_Destination_type>::value);
|
|
}
|
|
|
|
_Msg = _Create_send_message();
|
|
}
|
|
|
|
if (_Msg != NULL)
|
|
{
|
|
_M_messageBuffer._Enqueue(_Msg);
|
|
|
|
if (!_M_messageBuffer._Is_head(_Msg->msg_id()))
|
|
{
|
|
// another message is at the head of the outbound message queue and blocked
|
|
// simply return
|
|
return;
|
|
}
|
|
}
|
|
|
|
_Propagate_priority_order(_M_messageBuffer);
|
|
}
|
|
|
|
private:
|
|
|
|
/// <summary>
|
|
/// Tries to reserve from all sources. If successful, it will consume all the messages
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A bool indicating whether the consumption attempt worked.
|
|
/// </returns>
|
|
/// <typeparam name="_Index">
|
|
/// The highest-number index of the join's sources
|
|
/// </typeparam>
|
|
/**/
|
|
template<int _Index>
|
|
bool _Try_consume_source_messages(_Destination_type & _Destination_tuple, ISource<size_t> ** _Sources)
|
|
{
|
|
_Non_greedy_node<std::tr1::remove_pointer<std::tr1::tuple_element<_Index, _Type>::type>::type::source_type> * _Node =
|
|
static_cast<_Non_greedy_node<std::tr1::remove_pointer<std::tr1::tuple_element<_Index, _Type>::type>::type::source_type> *>(_Sources[_Index]);
|
|
|
|
// Increment the counter once for each reservation
|
|
_InterlockedIncrement(&_M_counter);
|
|
|
|
if (_Node->_Reserve_received_message())
|
|
{
|
|
bool _Ret_val = _Try_consume_source_messages<_Index + 1>(_Destination_tuple, _Sources);
|
|
|
|
if (_Ret_val)
|
|
{
|
|
_Node->_Consume_received_message();
|
|
}
|
|
else
|
|
{
|
|
if (_Node->_Release_received_message())
|
|
{
|
|
// If _Release_received_message() restored the ID, decrement the count for that
|
|
// restoration
|
|
if (_InterlockedDecrement(&_M_counter) == 0)
|
|
{
|
|
async_send(NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
return _Ret_val;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides a sentinel template specialization for _Try_consume_source_messages recursive
|
|
/// template expansion.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A bool indicating whether the consumption attempt worked.
|
|
/// </returns>
|
|
/**/
|
|
template<> bool _Try_consume_source_messages<std::tr1::tuple_size<_Type>::value>(_Destination_type &, ISource<size_t> **)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to acquire all of the messages from the _Non_greedy_nodes. Each node has already
|
|
/// indicated that it has received a message that it can try to reserve. This function
|
|
/// starts the reservation and consume process.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A bool indicating whether the reserve/consume of all messages succeeded.
|
|
/// </returns>
|
|
/**/
|
|
bool _Non_greedy_acquire_messages()
|
|
{
|
|
_Destination_type _Destination_tuple;
|
|
|
|
// Populate the sources buffer
|
|
ISource<size_t> * _Sources[std::tr1::tuple_size<_Type>::value];
|
|
size_t _Index = 0;
|
|
|
|
// Get an iterator which will keep a reference on the connected sources
|
|
source_iterator _Iter = _M_connectedSources.begin();
|
|
|
|
while (*_Iter != NULL)
|
|
{
|
|
ISource<size_t> * _PSource = *_Iter;
|
|
|
|
if (_PSource == NULL)
|
|
{
|
|
// One of the sources disconnected
|
|
break;
|
|
}
|
|
|
|
if (_Index >= std::tr1::tuple_size<_Type>::value)
|
|
{
|
|
// More sources that we expect
|
|
break;
|
|
}
|
|
|
|
_Sources[_Index] = _PSource;
|
|
_Index++;
|
|
++_Iter;
|
|
}
|
|
|
|
// The order nodes should not have unlinked while the join node is
|
|
// active.
|
|
|
|
if (_Index != std::tr1::tuple_size<_Type>::value)
|
|
{
|
|
// On debug build assert to help debugging
|
|
_CONCRT_ASSERT(_Index == std::tr1::tuple_size<_Type>::value);
|
|
return false;
|
|
}
|
|
|
|
bool _IsAcquireSuccessful = _Try_consume_source_messages<0>(_Destination_tuple, _Sources);
|
|
|
|
return _IsAcquireSuccessful;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Propagate messages in priority order
|
|
/// </summary>
|
|
/// <param name="_MessageBuffer">
|
|
/// Reference to a message queue with messages to be propagated
|
|
/// </param>
|
|
/**/
|
|
void _Propagate_priority_order(::Concurrency::details::_Queue<message<_Target_type>> & _MessageBuffer)
|
|
{
|
|
message<_Target_type> * _Msg = _MessageBuffer._Peek();
|
|
|
|
// If someone has reserved the _Head message, don't propagate anymore
|
|
if (_M_pReservedFor != NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
while (_Msg != NULL)
|
|
{
|
|
message_status _Status = declined;
|
|
|
|
// Always start from the first target that linked
|
|
for (target_iterator _Iter = _M_connectedTargets.begin(); *_Iter != NULL; ++_Iter)
|
|
{
|
|
ITarget<_Target_type> * _PTarget = *_Iter;
|
|
_Status = _PTarget->propagate(_Msg, this);
|
|
|
|
// Ownership of message changed. Do not propagate this
|
|
// message to any other target.
|
|
if (_Status == accepted)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// If the target just propagated to reserved this message, stop
|
|
// propagating it to others
|
|
if (_M_pReservedFor != NULL)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If status is anything other than accepted, then the head message
|
|
// was not propagated out. Thus, nothing after it in the queue can
|
|
// be propagated out. Cease propagation.
|
|
if (_Status != accepted)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Get the next message
|
|
_Msg = _MessageBuffer._Peek();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when all the source messaging blocks have received their messages. The payloads are copied
|
|
/// into local tuple and then packaged into a message to be propagated: _M_pSendMessage.
|
|
/// </summary>
|
|
/**/
|
|
message<_Destination_type> * _Create_send_message()
|
|
{
|
|
_Destination_type _Destination_tuple;
|
|
|
|
// Populate the sources buffer
|
|
ISource<size_t> * _Sources[std::tr1::tuple_size<_Type>::value];
|
|
size_t _Index = 0;
|
|
|
|
// Get an iterator which will keep a reference on the connected sources
|
|
source_iterator _Iter = _M_connectedSources.begin();
|
|
|
|
while (*_Iter != NULL)
|
|
{
|
|
ISource<size_t> * _PSource = *_Iter;
|
|
|
|
if (_PSource == NULL)
|
|
{
|
|
// One of the sources disconnected
|
|
break;
|
|
}
|
|
|
|
// Avoid buffer overrun
|
|
if (_Index >= std::tr1::tuple_size<_Type>::value)
|
|
{
|
|
// More sources that we expect
|
|
break;
|
|
}
|
|
|
|
_Sources[_Index] = *_Iter;
|
|
_Index++;
|
|
++_Iter;
|
|
}
|
|
|
|
// The order nodes should not have unlinked while the join node is
|
|
// active.
|
|
if (_Index != std::tr1::tuple_size<_Type>::value)
|
|
{
|
|
// On debug build assert to help debugging
|
|
_CONCRT_ASSERT(_Index == std::tr1::tuple_size<_Type>::value);
|
|
return NULL;
|
|
}
|
|
|
|
_Populate_destination_tuple<0>(_Destination_tuple, _Sources);
|
|
|
|
return new message<_Destination_type>(_Destination_tuple);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes all messages currently stored in this message block. Should be called
|
|
/// by the destructor to ensure any messages propagated in are cleaned up.
|
|
/// </summary>
|
|
/**/
|
|
void _Delete_stored_messages()
|
|
{
|
|
// Delete any messages remaining in the output queue
|
|
for (;;)
|
|
{
|
|
message<_Destination_type> * _Msg = _M_messageBuffer._Dequeue();
|
|
if (_Msg == NULL)
|
|
{
|
|
break;
|
|
}
|
|
delete _Msg;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies payloads from all sources to destination tuple.
|
|
/// </summary>
|
|
/**/
|
|
template<int _Index>
|
|
void _Populate_destination_tuple(_Destination_type & _Destination_tuple, ISource<size_t> ** _Sources)
|
|
{
|
|
_Order_node_base<std::tr1::remove_pointer<std::tr1::tuple_element<_Index, _Type>::type>::type::source_type> * _Node =
|
|
static_cast<_Order_node_base<std::tr1::remove_pointer<std::tr1::tuple_element<_Index, _Type>::type>::type::source_type> *>(_Sources[_Index]);
|
|
|
|
std::tr1::get<_Index>(_Destination_tuple) = _Node->value();
|
|
_Node->_Reset();
|
|
|
|
_Populate_destination_tuple<_Index + 1>(_Destination_tuple, _Sources);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides a sentinel template specialization for _Populate_destination_tuple recursive
|
|
/// template expansion.
|
|
/// </summary>
|
|
/**/
|
|
template<> void _Populate_destination_tuple<std::tr1::tuple_size<_Type>::value>(_Destination_type &, ISource<size_t> **)
|
|
{
|
|
}
|
|
|
|
// A tuple containing a collection of source messaging blocks
|
|
_Type _M_sourceTuple;
|
|
|
|
// Counts messages received by sources of this node and is used to trigger propagation to targets
|
|
// This value starts at the total number of inputs and counts down to zero. When it reaches zero,
|
|
// a join of the inputs is started.
|
|
volatile long _M_counter;
|
|
|
|
// Buffer to hold outgoing messages
|
|
::Concurrency::details::_Queue<message<_Destination_type>> _M_messageBuffer;
|
|
|
|
private:
|
|
//
|
|
// Hide assignment operator and copy constructor
|
|
//
|
|
_Join_node(const _Join_node & _Join); // no copy constructor
|
|
_Join_node const &operator =(_Join_node const &); // no assignment operator
|
|
};
|
|
|
|
/// <summary>
|
|
/// A <c>multitype_join</c> messaging block is a multi-source, single-target messaging block that
|
|
/// combines together messages of different types from each of its sources and offers a tuple
|
|
/// of the combined messages to its targets.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The <c>tuple</c> payload type of the messages joined and propagated by the block.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Jtype">
|
|
/// The kind of <c>join</c> block this is, either <c>greedy</c> or <c>non_greedy</c>
|
|
/// </typeparam>
|
|
/// <remarks>
|
|
/// For more information, see <see cref="Asynchronous Message Blocks"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="choice Class"/>
|
|
/// <seealso cref="join Class"/>
|
|
/// <seealso cref="join_type Enumeration"/>
|
|
/// <seealso cref="make_join Function"/>
|
|
/// <seealso cref="make_greedy_join Function"/>
|
|
/// <seealso cref="tuple Class"/>
|
|
/**/
|
|
template<typename _Type, join_type _Jtype = non_greedy>
|
|
class multitype_join: public ISource<typename _Unwrap<_Type>::type>
|
|
{
|
|
public:
|
|
|
|
typedef typename _Unwrap<_Type>::type _Destination_type;
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>multitype_join</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_Tuple">
|
|
/// A <c>tuple</c> of sources for this <c>multitype_join</c> messaging block.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// </para>
|
|
/// <para>
|
|
/// Move construction is not performed under a lock, which means that it is up to the user
|
|
/// to make sure that there are no light-weight tasks in flight at the time of moving.
|
|
/// Otherwise, numerous races can occur, leading to exceptions or inconsistent state.
|
|
/// </para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
explicit multitype_join(_Type _Tuple) : _M_sourceTuple(_Tuple), _M_pScheduler(NULL), _M_pScheduleGroup(NULL)
|
|
{
|
|
_M_pJoinNode = new _Join_node<_Type, _Destination_type, _Jtype>();
|
|
_Initialize_joins<0>();
|
|
}
|
|
|
|
#ifdef _CRT_USE_WINAPI_FAMILY_DESKTOP_APP
|
|
/// <summary>
|
|
/// Constructs a <c>multitype_join</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// The <c>Scheduler</c> object within which the propagation task for the <c>multitype_join</c> messaging block is scheduled.
|
|
/// </param>
|
|
/// <param name="_Tuple">
|
|
/// A <c>tuple</c> of sources for this <c>multitype_join</c> messaging block.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// </para>
|
|
/// <para>
|
|
/// Move construction is not performed under a lock, which means that it is up to the user
|
|
/// to make sure that there are no light-weight tasks in flight at the time of moving.
|
|
/// Otherwise, numerous races can occur, leading to exceptions or inconsistent state.
|
|
/// </para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
multitype_join(Scheduler& _PScheduler, _Type _Tuple) : _M_sourceTuple(_Tuple), _M_pScheduler(&_PScheduler), _M_pScheduleGroup(NULL)
|
|
{
|
|
_M_pJoinNode = new _Join_node<_Type, _Destination_type, _Jtype>(_PScheduler);
|
|
_Initialize_joins<0>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>multitype_join</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The <c>ScheduleGroup</c> object within which the propagation task for the <c>multitype_join</c> messaging block is scheduled.
|
|
/// The <c>Scheduler</c> object used is implied by the schedule group.
|
|
/// </param>
|
|
/// <param name="_Tuple">
|
|
/// A <c>tuple</c> of sources for this <c>multitype_join</c> messaging block.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// </para>
|
|
/// <para>
|
|
/// Move construction is not performed under a lock, which means that it is up to the user
|
|
/// to make sure that there are no light-weight tasks in flight at the time of moving.
|
|
/// Otherwise, numerous races can occur, leading to exceptions or inconsistent state.
|
|
/// </para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
multitype_join(ScheduleGroup& _PScheduleGroup, _Type _Tuple) : _M_sourceTuple(_Tuple), _M_pScheduler(NULL), _M_pScheduleGroup(&_PScheduleGroup)
|
|
{
|
|
_M_pJoinNode = new _Join_node<_Type, _Destination_type, _Jtype>(_PScheduleGroup);
|
|
_Initialize_joins<0>();
|
|
}
|
|
#endif /* _CRT_USE_WINAPI_FAMILY_DESKTOP_APP */
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>multitype_join</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_Join">
|
|
/// A <c>multitype_join</c> messaging block to copy from.
|
|
/// Note that the original object is orphaned, making this a move constructor.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PScheduleGroup"/> parameters.
|
|
/// </para>
|
|
/// <para>
|
|
/// Move construction is not performed under a lock, which means that it is up to the user
|
|
/// to make sure that there are no light-weight tasks in flight at the time of moving.
|
|
/// Otherwise, numerous races can occur, leading to exceptions or inconsistent state.
|
|
/// </para>
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
multitype_join(multitype_join && _Join)
|
|
{
|
|
// Copy scheduler group or scheduler to the new object.
|
|
_M_pScheduleGroup = _Join._M_pScheduleGroup;
|
|
_M_pScheduler = _Join._M_pScheduler;
|
|
|
|
// Single assignment is heap allocated, so simply copy the pointer. If it already has
|
|
// a value, it will be preserved.
|
|
_M_pJoinNode = _Join._M_pJoinNode;
|
|
_Join._M_pJoinNode = NULL;
|
|
|
|
// Invoke copy assignment for tuple to copy pointers to message blocks.
|
|
_M_sourceTuple = _Join._M_sourceTuple;
|
|
|
|
// Copy the pointers to order nodes to a new object and zero out in the old object.
|
|
memcpy(_M_pSourceJoins, _Join._M_pSourceJoins, sizeof(_M_pSourceJoins));
|
|
memset(_Join._M_pSourceJoins, 0, sizeof(_M_pSourceJoins));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Destroys the <c>multitype_join</c> messaging block.
|
|
/// </summary>
|
|
/**/
|
|
~multitype_join()
|
|
{
|
|
delete _M_pJoinNode;
|
|
_Delete_joins<0>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// A type alias for <typeparamref name="_Type"/>.
|
|
/// </summary>
|
|
/**/
|
|
typedef typename _Type type;
|
|
|
|
//
|
|
// ISource public function implementations
|
|
//
|
|
|
|
/// <summary>
|
|
/// Links a target block to this <c>multitype_join</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to an <c>ITarget</c> block to link to this <c>multitype_join</c> messaging block.
|
|
/// </param>
|
|
/**/
|
|
virtual void link_target(_Inout_ ITarget<_Destination_type> * _PTarget)
|
|
{
|
|
_M_pJoinNode->link_target(_PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unlinks a target block from this <c>multitype_join</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to an <c>ITarget</c> block to unlink from this <c>multitype_join</c> messaging block.
|
|
/// </param>
|
|
/**/
|
|
virtual void unlink_target(_Inout_ ITarget<_Destination_type> * _PTarget)
|
|
{
|
|
_M_pJoinNode->unlink_target(_PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unlinks all targets from this <c>multitype_join</c> messaging block.
|
|
/// </summary>
|
|
/**/
|
|
virtual void unlink_targets()
|
|
{
|
|
_M_pJoinNode->unlink_targets();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accepts a message that was offered by this <c>multitype_join</c> block, transferring ownership to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the offered <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling the <c>accept</c> method.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the message that the caller now has ownership of.
|
|
/// </returns>
|
|
/**/
|
|
virtual message<_Destination_type> * accept(runtime_object_identity _MsgId, _Inout_ ITarget<_Destination_type> * _PTarget)
|
|
{
|
|
return _M_pJoinNode->accept(_MsgId, _PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reserves a message previously offered by this <c>multitype_join</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being reserved.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling the <c>reserve</c> method.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the message was successfully reserved, <c>false</c> otherwise. Reservations can fail
|
|
/// for many reasons, including: the message was already reserved or accepted by another target, the source could
|
|
/// deny reservations, and so forth.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// After you call <c>reserve</c>, if it succeeds, you must call either <c>consume</c> or <c>release</c>
|
|
/// in order to take or give up possession of the message, respectively.
|
|
/// </remarks>
|
|
/**/
|
|
virtual bool reserve(runtime_object_identity _MsgId, _Inout_ ITarget<_Destination_type> * _PTarget)
|
|
{
|
|
return _M_pJoinNode->reserve(_MsgId, _PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Consumes a message previously offered by the <c>multitype_join</c> messaging block and successfully reserved by the target,
|
|
/// transferring ownership to the caller.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the reserved <c>message</c> object.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling the <c>consume</c> method.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A pointer to the <c>message</c> object that the caller now has ownership of.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// The <c>consume</c> method is similar to <c>accept</c>, but must always be preceded by a call to <c>reserve</c> that
|
|
/// returned <c>true</c>.
|
|
/// </remarks>
|
|
/**/
|
|
virtual message<_Destination_type> * consume(runtime_object_identity _MsgId, _Inout_ ITarget<_Destination_type> * _PTarget)
|
|
{
|
|
return _M_pJoinNode->consume(_MsgId, _PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases a previous successful message reservation.
|
|
/// </summary>
|
|
/// <param name="_MsgId">
|
|
/// The <c>runtime_object_identity</c> of the <c>message</c> object being released.
|
|
/// </param>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling the <c>release</c> method.
|
|
/// </param>
|
|
/**/
|
|
virtual void release(runtime_object_identity _MsgId, _Inout_ ITarget<_Destination_type> * _PTarget)
|
|
{
|
|
_M_pJoinNode->release(_MsgId, _PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Acquires a reference count on this <c>multitype_join</c> messaging block, to prevent deletion.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling this method.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This method is called by an <c>ITarget</c> object that is being linked to this source
|
|
/// during the <c>link_target</c> method.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void acquire_ref(_Inout_ ITarget<_Destination_type> * _PTarget)
|
|
{
|
|
_M_pJoinNode->acquire_ref(_PTarget);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases a reference count on this <c>multiple_join</c> messaging block.
|
|
/// </summary>
|
|
/// <param name="_PTarget">
|
|
/// A pointer to the target block that is calling this method.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This method is called by an <c>ITarget</c> object that is being unlinked from this source.
|
|
/// The source block is allowed to release any resources reserved for the target block.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void release_ref(_Inout_ ITarget<_Destination_type> * _PTarget)
|
|
{
|
|
_M_pJoinNode->release_ref(_PTarget);
|
|
}
|
|
|
|
private:
|
|
|
|
/// <summary>
|
|
/// Constructs and initializes a _Order_node for each tuple messaging block passed in.
|
|
/// </summary>
|
|
/// <typeparam name="_Index">
|
|
/// The highest-number index of the multitype_join's sources
|
|
/// </typeparam>
|
|
/**/
|
|
template<int _Index>
|
|
void _Initialize_joins()
|
|
{
|
|
std::tr1::tuple_element<_Index, _Type>::type _Item = std::tr1::get<_Index>(_M_sourceTuple);
|
|
_Order_node_base<std::tr1::remove_pointer<std::tr1::tuple_element<_Index, _Type>::type>::type::source_type> * _Order_node_element = NULL;
|
|
|
|
bool fIsNonGreedy = (_Jtype == non_greedy);
|
|
|
|
if (fIsNonGreedy)
|
|
{
|
|
if (_M_pScheduleGroup != NULL)
|
|
{
|
|
_Order_node_element = new _Non_greedy_node<std::tr1::remove_pointer<std::tr1::tuple_element<_Index, _Type>::type>::type::source_type> (*_M_pScheduleGroup, _Item, _Index);
|
|
}
|
|
else if (_M_pScheduler != NULL)
|
|
{
|
|
_Order_node_element = new _Non_greedy_node<std::tr1::remove_pointer<std::tr1::tuple_element<_Index, _Type>::type>::type::source_type> (*_M_pScheduler, _Item, _Index);
|
|
}
|
|
else
|
|
{
|
|
_Order_node_element = new _Non_greedy_node<std::tr1::remove_pointer<std::tr1::tuple_element<_Index, _Type>::type>::type::source_type> (_Item, _Index);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (_M_pScheduleGroup != NULL)
|
|
{
|
|
_Order_node_element = new _Greedy_node<std::tr1::remove_pointer<std::tr1::tuple_element<_Index, _Type>::type>::type::source_type> (*_M_pScheduleGroup, _Item, _Index);
|
|
}
|
|
else if (_M_pScheduler != NULL)
|
|
{
|
|
_Order_node_element = new _Greedy_node<std::tr1::remove_pointer<std::tr1::tuple_element<_Index, _Type>::type>::type::source_type> (*_M_pScheduler, _Item, _Index);
|
|
}
|
|
else
|
|
{
|
|
_Order_node_element = new _Greedy_node<std::tr1::remove_pointer<std::tr1::tuple_element<_Index, _Type>::type>::type::source_type> (_Item, _Index);
|
|
}
|
|
}
|
|
_M_pSourceJoins[_Index] = _Order_node_element;
|
|
_Order_node_element->link_target(_M_pJoinNode);
|
|
_Initialize_joins<_Index + 1>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides a sentinel template specialization for _Initialize_joins recursive
|
|
/// template expansion.
|
|
/// </summary>
|
|
/**/
|
|
template<> void _Initialize_joins<std::tr1::tuple_size<_Type>::value>()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes all _Order_node elements that were created in _Initialize_joins.
|
|
/// </summary>
|
|
/// <typeparam name="_Index">
|
|
/// The highest-number index of the multitype_join's sources
|
|
/// </typeparam>
|
|
/**/
|
|
template<int _Index>
|
|
void _Delete_joins()
|
|
{
|
|
delete reinterpret_cast<_Order_node_base<std::tr1::remove_pointer<std::tr1::tuple_element<_Index, _Type>::type>::type::source_type> *>(_M_pSourceJoins[_Index]);
|
|
_M_pSourceJoins[_Index] = NULL;
|
|
_Delete_joins<_Index + 1>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides a sentinel template specialization for _Delete_joins recursive
|
|
/// template expansion.
|
|
/// </summary>
|
|
/**/
|
|
template<> void _Delete_joins<std::tr1::tuple_size<_Type>::value>()
|
|
{
|
|
}
|
|
|
|
// Array of pointers to _Order_node elements representing each source
|
|
void * _M_pSourceJoins[std::tr1::tuple_size<_Type>::value];
|
|
|
|
// Join node that collects source messaging block messages
|
|
_Join_node<_Type, _Destination_type, _Jtype> * _M_pJoinNode;
|
|
|
|
// Tuple of messaging blocks that are sources to this multitype_join
|
|
_Type _M_sourceTuple;
|
|
|
|
// The scheduler to propagate messages on
|
|
Scheduler * _M_pScheduler;
|
|
|
|
// The schedule group to propagate messages on
|
|
ScheduleGroup * _M_pScheduleGroup;
|
|
|
|
private:
|
|
//
|
|
// Hide assignment operator
|
|
//
|
|
multitype_join const &operator =(multitype_join const &); // no assignment operator
|
|
multitype_join(multitype_join const &); // no copy constructor
|
|
};
|
|
|
|
// Templated factory functions that create a join, three flavors
|
|
|
|
#ifdef _CRT_USE_WINAPI_FAMILY_DESKTOP_APP
|
|
/// <summary>
|
|
/// Constructs a <c>non_greedy multitype_join</c> messaging block from an optional <c>Scheduler</c>
|
|
/// or <c>ScheduleGroup</c> and two or more input sources.
|
|
/// </summary>
|
|
/// <typeparam name="_Type1">
|
|
/// The message block type of the first source.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Type2">
|
|
/// The message block type of the second source.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Types">
|
|
/// The message block types of additional sources.
|
|
/// </typeparam>
|
|
/// <param name="_PScheduler">
|
|
/// The <c>Scheduler</c> object within which the propagation task for the <c>multitype_join</c> messaging block is scheduled.
|
|
/// </param>
|
|
/// <param name="_Item1">
|
|
/// The first source.
|
|
/// </param>
|
|
/// <param name="_Item2">
|
|
/// The second source.
|
|
/// </param>
|
|
/// <param name="_Items">
|
|
/// Additional sources.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <c>non_greedy multitype_join</c> message block with two or more input sources.
|
|
/// </returns>
|
|
/// <seealso cref="multitype_join Class"/>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/**/
|
|
template<typename _Type1, typename _Type2, typename... _Types>
|
|
multitype_join<std::tuple<_Type1, _Type2, _Types...>>
|
|
make_join(Scheduler& _PScheduler, _Type1 _Item1, _Type2 _Item2, _Types... _Items)
|
|
{
|
|
return multitype_join<std::tuple<_Type1, _Type2, _Types...>>(_PScheduler, std::make_tuple(_Item1, _Item2, _Items...));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>non_greedy multitype_join</c> messaging block from an optional <c>Scheduler</c>
|
|
/// or <c>ScheduleGroup</c> and two or more input sources.
|
|
/// </summary>
|
|
/// <typeparam name="_Type1">
|
|
/// The message block type of the first source.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Type2">
|
|
/// The message block type of the second source.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Types">
|
|
/// The message block types of additional sources.
|
|
/// </typeparam>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The <c>ScheduleGroup</c> object within which the propagation task for the <c>multitype_join</c> messaging block is scheduled.
|
|
/// The <c>Scheduler</c> object used is implied by the schedule group.
|
|
/// </param>
|
|
/// <param name="_Item1">
|
|
/// The first source.
|
|
/// </param>
|
|
/// <param name="_Item2">
|
|
/// The second source.
|
|
/// </param>
|
|
/// <param name="_Items">
|
|
/// Additional sources.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <c>non_greedy multitype_join</c> message block with two or more input sources.
|
|
/// </returns>
|
|
/// <seealso cref="multitype_join Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
template<typename _Type1, typename _Type2, typename... _Types>
|
|
multitype_join<std::tuple<_Type1, _Type2, _Types...>>
|
|
make_join(ScheduleGroup& _PScheduleGroup, _Type1 _Item1, _Type2 _Item2, _Types... _Items)
|
|
{
|
|
return multitype_join<std::tuple<_Type1, _Type2, _Types...>>(_PScheduleGroup, std::make_tuple(_Item1, _Item2, _Items...));
|
|
}
|
|
|
|
#endif /* _CRT_USE_WINAPI_FAMILY_DESKTOP_APP */
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>non_greedy multitype_join</c> messaging block from an optional <c>Scheduler</c>
|
|
/// or <c>ScheduleGroup</c> and two or more input sources.
|
|
/// </summary>
|
|
/// <typeparam name="_Type1">
|
|
/// The message block type of the first source.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Type2">
|
|
/// The message block type of the second source.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Types">
|
|
/// The message block types of additional sources.
|
|
/// </typeparam>
|
|
/// <param name="_Item1">
|
|
/// The first source.
|
|
/// </param>
|
|
/// <param name="_Item2">
|
|
/// The second source.
|
|
/// </param>
|
|
/// <param name="_Items">
|
|
/// Additional sources.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <c>non_greedy multitype_join</c> message block with two or more input sources.
|
|
/// </returns>
|
|
/// <seealso cref="multitype_join Class"/>
|
|
/**/
|
|
template<typename _Type1, typename _Type2, typename... _Types>
|
|
multitype_join<std::tuple<_Type1, _Type2, _Types...>>
|
|
make_join(_Type1 _Item1, _Type2 _Item2, _Types... _Items)
|
|
{
|
|
return multitype_join<std::tuple<_Type1, _Type2, _Types...>>(std::make_tuple(_Item1, _Item2, _Items...));
|
|
}
|
|
|
|
// Templated factory functions that create a *greedy* join, three flavors
|
|
#ifdef _CRT_USE_WINAPI_FAMILY_DESKTOP_APP
|
|
/// <summary>
|
|
/// Constructs a <c>greedy multitype_join</c> messaging block from an optional <c>Scheduler</c>
|
|
/// or <c>ScheduleGroup</c> and two or more input sources.
|
|
/// </summary>
|
|
/// <typeparam name="_Type1">
|
|
/// The message block type of the first source.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Type2">
|
|
/// The message block type of the second source.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Types">
|
|
/// The message block types of additional sources.
|
|
/// </typeparam>
|
|
/// <param name="_PScheduler">
|
|
/// The <c>Scheduler</c> object within which the propagation task for the <c>multitype_join</c> messaging block
|
|
/// is scheduled.
|
|
/// </param>
|
|
/// <param name="_Item1">
|
|
/// The first source.
|
|
/// </param>
|
|
/// <param name="_Item2">
|
|
/// The second source.
|
|
/// </param>
|
|
/// <param name="_Items">
|
|
/// Additional sources.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <c>greedy multitype_join</c> message block with two or more input sources.
|
|
/// </returns>
|
|
/// <seealso cref="multitype_join Class"/>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/**/
|
|
template<typename _Type1, typename _Type2, typename... _Types>
|
|
multitype_join<std::tuple<_Type1, _Type2, _Types...>, greedy>
|
|
make_greedy_join(Scheduler& _PScheduler, _Type1 _Item1, _Type2 _Item2, _Types... _Items)
|
|
{
|
|
return multitype_join<std::tuple<_Type1, _Type2, _Types...>, greedy>(_PScheduler, std::make_tuple(_Item1, _Item2, _Items...));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>greedy multitype_join</c> messaging block from an optional <c>Scheduler</c>
|
|
/// or <c>ScheduleGroup</c> and two or more input sources.
|
|
/// </summary>
|
|
/// <typeparam name="_Type1">
|
|
/// The message block type of the first source.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Type2">
|
|
/// The message block type of the second source.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Types">
|
|
/// The message block types of additional sources.
|
|
/// </typeparam>
|
|
/// <param name="_PScheduleGroup">
|
|
/// The <c>ScheduleGroup</c> object within which the propagation task for the <c>multitype_join</c> messaging block is scheduled.
|
|
/// The <c>Scheduler</c> object used is implied by the schedule group.
|
|
/// </param>
|
|
/// <param name="_Item1">
|
|
/// The first source.
|
|
/// </param>
|
|
/// <param name="_Item2">
|
|
/// The second source.
|
|
/// </param>
|
|
/// <param name="_Items">
|
|
/// Additional sources.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <c>greedy multitype_join</c> message block with two or more input sources.
|
|
/// </returns>
|
|
/// <seealso cref="multitype_join Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
template<typename _Type1, typename _Type2, typename... _Types>
|
|
multitype_join<std::tuple<_Type1, _Type2, _Types...>, greedy>
|
|
make_greedy_join(ScheduleGroup& _PScheduleGroup, _Type1 _Item1, _Type2 _Item2, _Types... _Items)
|
|
{
|
|
return multitype_join<std::tuple<_Type1, _Type2, _Types...>, greedy>(_PScheduleGroup, std::make_tuple(_Item1, _Item2, _Items...));
|
|
}
|
|
|
|
#endif /* _CRT_USE_WINAPI_FAMILY_DESKTOP_APP */
|
|
|
|
/// <summary>
|
|
/// Constructs a <c>greedy multitype_join</c> messaging block from an optional <c>Scheduler</c>
|
|
/// or <c>ScheduleGroup</c> and two or more input sources.
|
|
/// </summary>
|
|
/// <typeparam name="_Type1">
|
|
/// The message block type of the first source.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Type2">
|
|
/// The message block type of the second source.
|
|
/// </typeparam>
|
|
/// <typeparam name="_Types">
|
|
/// The message block types of additional sources.
|
|
/// </typeparam>
|
|
/// <param name="_Item1">
|
|
/// The first source.
|
|
/// </param>
|
|
/// <param name="_Item2">
|
|
/// The second source.
|
|
/// </param>
|
|
/// <param name="_Items">
|
|
/// Additional sources.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <c>greedy multitype_join</c> message block with two or more input sources.
|
|
/// </returns>
|
|
/// <seealso cref="multitype_join Class"/>
|
|
/**/
|
|
template<typename _Type1, typename _Type2, typename... _Types>
|
|
multitype_join<std::tuple<_Type1, _Type2, _Types...>, greedy>
|
|
make_greedy_join(_Type1 _Item1, _Type2 _Item2, _Types... _Items)
|
|
{
|
|
return multitype_join<std::tuple<_Type1, _Type2, _Types...>, greedy>(std::make_tuple(_Item1, _Item2, _Items...));
|
|
}
|
|
|
|
//**************************************************************************
|
|
// Agents:
|
|
//**************************************************************************
|
|
|
|
/// <summary>
|
|
/// The valid states for an <c>agent</c>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// For more information, see <see cref="Asynchronous Agents"/>.
|
|
/// </remarks>
|
|
/**/
|
|
enum agent_status {
|
|
/// <summary>
|
|
/// The <c>agent</c> has been created but not started.
|
|
/// </summary>
|
|
/**/
|
|
agent_created,
|
|
/// <summary>
|
|
/// The <c>agent</c> has been started, but not entered its <c>run</c> method.
|
|
/// </summary>
|
|
/**/
|
|
agent_runnable,
|
|
/// <summary>
|
|
/// The <c>agent</c> has started.
|
|
/// </summary>
|
|
/**/
|
|
agent_started,
|
|
/// <summary>
|
|
/// The <c>agent</c> finished without being canceled.
|
|
/// </summary>
|
|
/**/
|
|
agent_done,
|
|
/// <summary>
|
|
/// The <c>agent</c> was canceled.
|
|
/// </summary>
|
|
/**/
|
|
agent_canceled
|
|
};
|
|
|
|
/// <summary>
|
|
/// A class intended to be used as a base class for all independent agents. It is used to hide
|
|
/// state from other agents and interact using message-passing.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// For more information, see <see cref="Asynchronous Agents"/>.
|
|
/// </remarks>
|
|
/**/
|
|
class agent
|
|
{
|
|
public:
|
|
/// <summary>
|
|
/// Constructs an agent.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PGroup"/> parameters.
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
_CRTIMP2 agent();
|
|
|
|
#ifdef _CRT_USE_WINAPI_FAMILY_DESKTOP_APP
|
|
/// <summary>
|
|
/// Constructs an agent.
|
|
/// </summary>
|
|
/// <param name="_PScheduler">
|
|
/// The <c>Scheduler</c> object within which the execution task of the agent is scheduled.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PGroup"/> parameters.
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
_CRTIMP2 agent(Scheduler& _PScheduler);
|
|
|
|
/// <summary>
|
|
/// Constructs an agent.
|
|
/// </summary>
|
|
/// <param name="_PGroup">
|
|
/// The <c>ScheduleGroup</c> object within which the execution task of the agent is scheduled.
|
|
/// The <c>Scheduler</c> object used is implied by the schedule group.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// The runtime uses the default scheduler if you do not specify the <paramref name="_PScheduler"/>
|
|
/// or <paramref name="_PGroup"/> parameters.
|
|
/// </remarks>
|
|
/// <seealso cref="Scheduler Class"/>
|
|
/// <seealso cref="ScheduleGroup Class"/>
|
|
/**/
|
|
_CRTIMP2 agent(ScheduleGroup& _PGroup);
|
|
#endif /* _CRT_USE_WINAPI_FAMILY_DESKTOP_APP */
|
|
|
|
/// <summary>
|
|
/// Destroys the agent.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// It is an error to destroy an agent that is not in a terminal state (either <c>agent_done</c> or
|
|
/// <c>agent_canceled</c>). This can be avoided by waiting for the agent to reach a terminal state
|
|
/// in the destructor of a class that inherits from the <c>agent</c> class.
|
|
/// </remarks>
|
|
/**/
|
|
_CRTIMP2 virtual ~agent();
|
|
|
|
/// <summary>
|
|
/// An asynchronous source of status information from the agent.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// Returns a message source that can send messages about the current state of the agent.
|
|
/// </returns>
|
|
/**/
|
|
_CRTIMP2 ISource<agent_status> * status_port();
|
|
|
|
/// <summary>
|
|
/// A synchronous source of status information from the agent.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// Returns the current state of the agent. Note that this returned state could change
|
|
/// immediately after being returned.
|
|
/// </returns>
|
|
/// <seealso cref="agent_status Enumeration"/>
|
|
/**/
|
|
_CRTIMP2 agent_status status();
|
|
|
|
/// <summary>
|
|
/// Moves an agent from the <c>agent_created</c> state to the <c>agent_runnable</c> state, and schedules it for execution.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// <c>true</c> if the agent started correctly, <c>false</c> otherwise. An agent that has been canceled cannot be started.
|
|
/// </returns>
|
|
/// <seealso cref="agent_status Enumeration"/>
|
|
/**/
|
|
_CRTIMP2 bool start();
|
|
|
|
/// <summary>
|
|
/// Moves an agent from either the <c>agent_created</c> or <c>agent_runnable</c> states to the <c>agent_canceled</c> state.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// <c>true</c> if the agent was canceled, <c>false</c> otherwise. An agent cannot be canceled if it has already started
|
|
/// running or has already completed.
|
|
/// </returns>
|
|
/// <seealso cref="agent_status Enumeration"/>
|
|
/**/
|
|
_CRTIMP2 bool cancel();
|
|
|
|
/// <summary>
|
|
/// Waits for an agent to complete its task.
|
|
/// </summary>
|
|
/// <param name="_PAgent">
|
|
/// A pointer to the agent to wait for.
|
|
/// </param>
|
|
/// <param name="_Timeout">
|
|
/// The maximum time for which to wait, in milliseconds.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The <c>agent_status</c> of the agent when the wait completes. This can either be <c>agent_canceled</c>
|
|
/// or <c>agent_done</c>.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// An agent task is completed when the agent enters the <c>agent_canceled</c> or <c>agent_done</c> states.
|
|
/// <para>If the parameter <paramref name="_Timeout"/> has a value other than the constant <c>COOPERATIVE_TIMEOUT_INFINITE</c>,
|
|
/// the exception <see cref="operation_timed_out Class">operation_timed_out</see> is thrown if the specified amount
|
|
/// of time expires before the agent has completed its task.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="agent::wait_for_all Method"/>
|
|
/// <seealso cref="agent::wait_for_one Method"/>
|
|
/// <seealso cref="agent_status Enumeration"/>
|
|
/**/
|
|
_CRTIMP2 static agent_status __cdecl wait(_Inout_ agent * _PAgent, unsigned int _Timeout = COOPERATIVE_TIMEOUT_INFINITE);
|
|
|
|
/// <summary>
|
|
/// Waits for all of the specified agents to complete their tasks.
|
|
/// </summary>
|
|
/// <param name="_Count">
|
|
/// The number of agent pointers present in the array <paramref name="_PAgents"/>.
|
|
/// </param>
|
|
/// <param name="_PAgents">
|
|
/// An array of pointers to the agents to wait for.
|
|
/// </param>
|
|
/// <param name="_PStatus">
|
|
/// A pointer to an array of agent statuses. Each status value will represent the status of the corresponding
|
|
/// agent when the method returns.
|
|
/// </param>
|
|
/// <param name="_Timeout">
|
|
/// The maximum time for which to wait, in milliseconds.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// An agent task is completed when the agent enters the <c>agent_canceled</c> or <c>agent_done</c> states.
|
|
/// <para>If the parameter <paramref name="_Timeout"/> has a value other than the constant <c>COOPERATIVE_TIMEOUT_INFINITE</c>,
|
|
/// the exception <see cref="operation_timed_out Class">operation_timed_out</see> is thrown if the specified amount
|
|
/// of time expires before the agent has completed its task.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="agent::wait Method"/>
|
|
/// <seealso cref="agent::wait_for_one Method"/>
|
|
/// <seealso cref="agent_status Enumeration"/>
|
|
/**/
|
|
_CRTIMP2 static void __cdecl wait_for_all(size_t _Count, _In_reads_(_Count) agent ** _PAgents,
|
|
_Out_writes_opt_(_Count) agent_status * _PStatus = NULL, unsigned int _Timeout = COOPERATIVE_TIMEOUT_INFINITE);
|
|
|
|
/// <summary>
|
|
/// Waits for any one of the specified agents to complete its task.
|
|
/// </summary>
|
|
/// <param name="_Count">
|
|
/// The number of agent pointers present in the array <paramref name="_PAgents"/>.
|
|
/// </param>
|
|
/// <param name="_PAgents">
|
|
/// An array of pointers to the agents to wait for.
|
|
/// </param>
|
|
/// <param name="_Status">
|
|
/// A reference to a variable where the agent status will be placed.
|
|
/// </param>
|
|
/// <param name="_Index">
|
|
/// A reference to a variable where the agent index will be placed.
|
|
/// </param>
|
|
/// <param name="_Timeout">
|
|
/// The maximum time for which to wait, in milliseconds.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// An agent task is completed when the agent enters the <c>agent_canceled</c> or <c>agent_done</c> states.
|
|
/// <para>If the parameter <paramref name="_Timeout"/> has a value other than the constant <c>COOPERATIVE_TIMEOUT_INFINITE</c>,
|
|
/// the exception <see cref="operation_timed_out Class">operation_timed_out</see> is thrown if the specified amount
|
|
/// of time expires before the agent has completed its task.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="agent::wait Method"/>
|
|
/// <seealso cref="agent::wait_for_all Method"/>
|
|
/// <seealso cref="agent_status Enumeration"/>
|
|
/**/
|
|
_CRTIMP2 static void __cdecl wait_for_one(size_t _Count, _In_reads_(_Count) agent ** _PAgents, agent_status& _Status,
|
|
size_t& _Index, unsigned int _Timeout = COOPERATIVE_TIMEOUT_INFINITE);
|
|
|
|
protected:
|
|
/// <summary>
|
|
/// Represents the main task of an agent. <c>run</c> should be overridden in a derived class, and specifies what
|
|
/// the agent should do after it has been started.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The agent status is changed to <c>agent_started</c> right before this method is invoked. The method should
|
|
/// invoke <c>done</c> on the agent with an appropriate status before returning, and may not throw any
|
|
/// exceptions.
|
|
/// </remarks>
|
|
/**/
|
|
virtual void run() = 0;
|
|
|
|
/// <summary>
|
|
/// Moves an agent into the <c>agent_done</c> state, indicating that the agent has completed.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// <c>true</c> if the agent is moved to the <c>agent_done</c> state, <c>false</c> otherwise. An agent that has
|
|
/// been canceled cannot be moved to the <c>agent_done</c> state.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// This method should be called at the end of the <c>run</c> method, when you know the execution of your agent
|
|
/// has completed.
|
|
/// </remarks>
|
|
/// <seealso cref="agent_status Enumeration"/>
|
|
/**/
|
|
_CRTIMP2 bool done();
|
|
|
|
/// <summary>
|
|
/// Holds the current status of the agent.
|
|
/// </summary>
|
|
/**/
|
|
overwrite_buffer<agent_status> _M_status;
|
|
|
|
private:
|
|
|
|
// A flag to check of whether the agent can be started
|
|
// This is initialized to TRUE and there is a race between Start() and Cancel() to set it
|
|
// to FALSE. Once Started or Canceled, further calls to Start() or Cancel() will return false.
|
|
/**/
|
|
volatile long _M_fStartable;
|
|
|
|
// A flag to check of whether the agent can be canceled
|
|
// This is initailized to TRUE and there is a race between Cancel() and the LWT executing
|
|
// a task that has been started to set it to FALSE. If Cancel() wins, the task will not be
|
|
// executed. If the LWT wins, Cancel() will return false.
|
|
/**/
|
|
volatile long _M_fCancelable;
|
|
|
|
// A static wrapper function that calls the Run() method. Used for scheduling of the task
|
|
/**/
|
|
static void __cdecl _Agent_task_wrapper(void * data);
|
|
|
|
Scheduler * _M_pScheduler;
|
|
ScheduleGroup * _M_pScheduleGroup;
|
|
|
|
//
|
|
// Hide assignment operator and copy constructor
|
|
//
|
|
agent const &operator =(agent const&); // no assignment operator
|
|
agent(agent const &); // no copy constructor
|
|
};
|
|
|
|
//**************************************************************************
|
|
// Direct Messaging APIs:
|
|
//**************************************************************************
|
|
|
|
/// <summary>
|
|
/// A general receive implementation, allowing a context to wait for data from
|
|
/// exactly one source and filter the values that are accepted. If the specified timeout is not
|
|
/// COOPERATIVE_TIMEOUT_INFINITE, an exception (operation_timed_out) will be thrown if the specified amount
|
|
/// of time expires before a message is received. Note that zero length timeouts should likely use
|
|
/// try_receive as opposed to receive with a timeout of zero as it is more efficient and does not
|
|
/// throw exceptions on timeouts.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload type
|
|
/// </typeparam>
|
|
/// <param name="_Src">
|
|
/// A pointer to the source from which data is expected.
|
|
/// </param>
|
|
/// <param name="_Timeout">
|
|
/// The maximum time for which the method should for the data, in milliseconds.
|
|
/// </param>
|
|
/// <param name="_Filter_proc">
|
|
/// A pointer to a filter which will indicate whether to accept the data or not.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A value from the source, of the payload type.
|
|
/// </returns>
|
|
/**/
|
|
template <class _Type>
|
|
_Type _Receive_impl(ISource<_Type> * _Src, unsigned int _Timeout, typename ITarget<_Type>::filter_method const* _Filter_proc)
|
|
{
|
|
// The Blocking Recipient messaging block class is internal to the receive function
|
|
class _Blocking_recipient : public ITarget<_Type>
|
|
{
|
|
public:
|
|
// Create an Blocking Recipient
|
|
_Blocking_recipient(ISource<_Type> * _PSource,
|
|
unsigned int _Timeout = COOPERATIVE_TIMEOUT_INFINITE) :
|
|
_M_pFilter(NULL), _M_pConnectedTo(NULL), _M_pMessage(NULL), _M_fState(_NotInitialized), _M_timeout(_Timeout)
|
|
{
|
|
_Connect(_PSource);
|
|
}
|
|
|
|
// Create an Blocking Recipient
|
|
_Blocking_recipient(ISource<_Type> * _PSource,
|
|
filter_method const& _Filter,
|
|
unsigned int _Timeout = COOPERATIVE_TIMEOUT_INFINITE) :
|
|
_M_pFilter(NULL), _M_pConnectedTo(NULL), _M_pMessage(NULL), _M_fState(_NotInitialized), _M_timeout(_Timeout)
|
|
{
|
|
if (_Filter != NULL)
|
|
{
|
|
_M_pFilter = new filter_method(_Filter);
|
|
}
|
|
|
|
_Connect(_PSource);
|
|
}
|
|
|
|
// Cleans up any resources that may have been created by the BlockingRecipient.
|
|
~_Blocking_recipient()
|
|
{
|
|
_Disconnect();
|
|
|
|
delete _M_pFilter;
|
|
delete _M_pMessage;
|
|
}
|
|
|
|
// Gets the value of the message sent to this BlockingRecipient. Blocks by
|
|
// spinning until a message has arrived.
|
|
_Type _Value()
|
|
{
|
|
_Wait_for_message();
|
|
|
|
return _M_pMessage->payload;
|
|
}
|
|
|
|
// The main propagation function for ITarget blocks. Called by a source
|
|
// block, generally within an asynchronous task to send messages to its targets.
|
|
virtual message_status propagate(message<_Type> * _PMessage, ISource<_Type> * _PSource)
|
|
{
|
|
// Throw exception if the message being propagated to this block is NULL
|
|
if (_PMessage == NULL)
|
|
{
|
|
throw std::invalid_argument("_PMessage");
|
|
}
|
|
|
|
if (_PSource == NULL)
|
|
{
|
|
throw std::invalid_argument("_PSource");
|
|
}
|
|
|
|
// Reject if the recipient has already received a message
|
|
if (_M_fState == _Initialized)
|
|
{
|
|
return declined;
|
|
}
|
|
|
|
// Reject if the message does not meet the filter requirements
|
|
if (_M_pFilter != NULL && !(*_M_pFilter)(_PMessage->payload))
|
|
{
|
|
return declined;
|
|
}
|
|
|
|
// Accept the message
|
|
_CONCRT_ASSERT(_PSource != NULL);
|
|
_M_pMessage = _PSource->accept(_PMessage->msg_id(), this);
|
|
|
|
if (_M_pMessage != NULL)
|
|
{
|
|
// Set the initialized flag on this block
|
|
if (_InterlockedExchange(&_M_fState, _Initialized) == _Blocked)
|
|
{
|
|
_M_ev.set();
|
|
}
|
|
|
|
return accepted;
|
|
}
|
|
|
|
return missed;
|
|
}
|
|
|
|
// Synchronously sends a message to this block. When this function completes the message will
|
|
// already have propagated into the block.
|
|
virtual message_status send(message<_Type> * _PMessage, ISource<_Type> * _PSource)
|
|
{
|
|
if (_PMessage == NULL)
|
|
{
|
|
throw std::invalid_argument("_PMessage");
|
|
}
|
|
|
|
if (_PSource == NULL)
|
|
{
|
|
throw std::invalid_argument("_PSource");
|
|
}
|
|
|
|
// Only the connected source is allowed to send messages
|
|
// to the blocking recepient. Decline messages without
|
|
// a source.
|
|
|
|
return declined;
|
|
}
|
|
|
|
private:
|
|
|
|
// Link a source block
|
|
virtual void link_source(ISource<_Type> * _PSrc)
|
|
{
|
|
_M_pConnectedTo = _PSrc;
|
|
_PSrc->acquire_ref(this);
|
|
}
|
|
|
|
// Remove a source messaging block for this BlockingRecipient
|
|
virtual void unlink_source(ISource<_Type> * _PSource)
|
|
{
|
|
if (_InterlockedCompareExchangePointer(reinterpret_cast<void *volatile *>(&_M_pConnectedTo), (void *)NULL, _PSource) == _PSource)
|
|
{
|
|
_PSource->release_ref(this);
|
|
}
|
|
}
|
|
|
|
// Remove the source messaging block for this BlockingRecipient
|
|
virtual void unlink_sources()
|
|
{
|
|
ISource<_Type> * _PSource = reinterpret_cast<ISource<_Type> *>(_InterlockedExchangePointer(reinterpret_cast<void *volatile *>(&_M_pConnectedTo), (void *)NULL));
|
|
if (_PSource != NULL)
|
|
{
|
|
_PSource->unlink_target(this);
|
|
_PSource->release_ref(this);
|
|
}
|
|
}
|
|
|
|
|
|
// Connect the blocking recipient to the source
|
|
void _Connect(ISource<_Type> * _PSource)
|
|
{
|
|
if (_PSource == NULL)
|
|
{
|
|
throw std::invalid_argument("_PSource");
|
|
}
|
|
|
|
_PSource->link_target(this);
|
|
}
|
|
|
|
// Cleanup the connection to the blocking recipient's source. There is no need
|
|
// to do anything about the associated context.
|
|
void _Disconnect()
|
|
{
|
|
unlink_sources();
|
|
}
|
|
|
|
// Internal function used to block while waiting for a message to arrive
|
|
// at this BlockingRecipient
|
|
void _Wait_for_message()
|
|
{
|
|
bool _Timeout = false;
|
|
|
|
// If we haven't received a message yet, cooperatively block.
|
|
if (_InterlockedCompareExchange(&_M_fState, _Blocked, _NotInitialized) == _NotInitialized)
|
|
{
|
|
if (_M_ev.wait(_M_timeout) == COOPERATIVE_WAIT_TIMEOUT)
|
|
{
|
|
_Timeout = true;
|
|
}
|
|
}
|
|
|
|
// Unlinking from our source guarantees that there are no threads in propagate
|
|
_Disconnect();
|
|
|
|
if (_M_fState != _Initialized)
|
|
{
|
|
// We had to have timed out if we came out of the wait
|
|
// without being initialized.
|
|
_CONCRT_ASSERT(_Timeout);
|
|
|
|
throw operation_timed_out();
|
|
}
|
|
}
|
|
|
|
// States for this block
|
|
enum
|
|
{
|
|
_NotInitialized,
|
|
_Blocked,
|
|
_Initialized
|
|
};
|
|
|
|
volatile long _M_fState;
|
|
|
|
// The source messaging block connected to this Recipient
|
|
ISource<_Type> * _M_pConnectedTo;
|
|
|
|
// The message that was received
|
|
message<_Type> * volatile _M_pMessage;
|
|
|
|
// The timeout.
|
|
unsigned int _M_timeout;
|
|
|
|
// The event we wait upon
|
|
event _M_ev;
|
|
|
|
// The filter that is called on this block before accepting a message
|
|
filter_method * _M_pFilter;
|
|
};
|
|
|
|
if (_Filter_proc != NULL)
|
|
{
|
|
_Blocking_recipient _Recipient(_Src, *_Filter_proc, _Timeout);
|
|
return _Recipient._Value();
|
|
}
|
|
else
|
|
{
|
|
_Blocking_recipient _Recipient(_Src, _Timeout);
|
|
return _Recipient._Value();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A general receive implementation, allowing a context to wait for data from
|
|
/// exactly one source and filter the values that are accepted.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload type.
|
|
/// </typeparam>
|
|
/// <param name="_Src">
|
|
/// A pointer or reference to the source from which data is expected.
|
|
/// </param>
|
|
/// <param name="_Timeout">
|
|
/// The maximum time for which the method should for the data, in milliseconds.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A value from the source, of the payload type.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// If the parameter <paramref name="_Timeout"/> has a value other than the constant <c>COOPERATIVE_TIMEOUT_INFINITE</c>,
|
|
/// the exception <see cref="operation_timed_out Class">operation_timed_out</see> is thrown if the specified amount
|
|
/// of time expires before a message is received. If you want a zero length timeout, you should use the
|
|
/// <see cref="try_receive Function">try_receive</see> function, as opposed to calling <c>receive</c> with a timeout
|
|
/// of <c>0</c> (zero), as it is more efficient and does not throw exceptions on timeouts.
|
|
/// <para>For more information, see <see cref="Message Passing Functions"/>.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="try_receive Function"/>
|
|
/// <seealso cref="send Function"/>
|
|
/// <seealso cref="asend Function"/>
|
|
/**/
|
|
template <class _Type>
|
|
_Type receive(_Inout_ ISource<_Type> * _Src, unsigned int _Timeout = COOPERATIVE_TIMEOUT_INFINITE)
|
|
{
|
|
return _Receive_impl(_Src, _Timeout, NULL);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A general receive implementation, allowing a context to wait for data from
|
|
/// exactly one source and filter the values that are accepted.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload type.
|
|
/// </typeparam>
|
|
/// <param name="_Src">
|
|
/// A pointer or reference to the source from which data is expected.
|
|
/// </param>
|
|
/// <param name="_Filter_proc">
|
|
/// A filter function which determines whether messages should be accepted.
|
|
/// </param>
|
|
/// <param name="_Timeout">
|
|
/// The maximum time for which the method should for the data, in milliseconds.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A value from the source, of the payload type.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// If the parameter <paramref name="_Timeout"/> has a value other than the constant <c>COOPERATIVE_TIMEOUT_INFINITE</c>,
|
|
/// the exception <see cref="operation_timed_out Class">operation_timed_out</see> is thrown if the specified amount
|
|
/// of time expires before a message is received. If you want a zero length timeout, you should use the
|
|
/// <see cref="try_receive Function">try_receive</see> function, as opposed to calling <c>receive</c> with a timeout
|
|
/// of <c>0</c> (zero), as it is more efficient and does not throw exceptions on timeouts.
|
|
/// <para>For more information, see <see cref="Message Passing Functions"/>.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="try_receive Function"/>
|
|
/// <seealso cref="send Function"/>
|
|
/// <seealso cref="asend Function"/>
|
|
/**/
|
|
template <class _Type>
|
|
_Type receive(_Inout_ ISource<_Type> * _Src, typename ITarget<_Type>::filter_method const& _Filter_proc, unsigned int _Timeout = COOPERATIVE_TIMEOUT_INFINITE)
|
|
{
|
|
return _Receive_impl(_Src, _Timeout, &_Filter_proc);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A general receive implementation, allowing a context to wait for data from
|
|
/// exactly one source and filter the values that are accepted.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload type.
|
|
/// </typeparam>
|
|
/// <param name="_Src">
|
|
/// A pointer or reference to the source from which data is expected.
|
|
/// </param>
|
|
/// <param name="_Timeout">
|
|
/// The maximum time for which the method should for the data, in milliseconds.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A value from the source, of the payload type.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// If the parameter <paramref name="_Timeout"/> has a value other than the constant <c>COOPERATIVE_TIMEOUT_INFINITE</c>,
|
|
/// the exception <see cref="operation_timed_out Class">operation_timed_out</see> is thrown if the specified amount
|
|
/// of time expires before a message is received. If you want a zero length timeout, you should use the
|
|
/// <see cref="try_receive Function">try_receive</see> function, as opposed to calling <c>receive</c> with a timeout
|
|
/// of <c>0</c> (zero), as it is more efficient and does not throw exceptions on timeouts.
|
|
/// <para>For more information, see <see cref="Message Passing Functions"/>.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="try_receive Function"/>
|
|
/// <seealso cref="send Function"/>
|
|
/// <seealso cref="asend Function"/>
|
|
/**/
|
|
template <class _Type>
|
|
_Type receive(ISource<_Type> &_Src, unsigned int _Timeout = COOPERATIVE_TIMEOUT_INFINITE)
|
|
{
|
|
return _Receive_impl(&_Src, _Timeout, NULL);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A general receive implementation, allowing a context to wait for data from
|
|
/// exactly one source and filter the values that are accepted.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload type.
|
|
/// </typeparam>
|
|
/// <param name="_Src">
|
|
/// A pointer or reference to the source from which data is expected.
|
|
/// </param>
|
|
/// <param name="_Filter_proc">
|
|
/// A filter function which determines whether messages should be accepted.
|
|
/// </param>
|
|
/// <param name="_Timeout">
|
|
/// The maximum time for which the method should for the data, in milliseconds.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A value from the source, of the payload type.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// If the parameter <paramref name="_Timeout"/> has a value other than the constant <c>COOPERATIVE_TIMEOUT_INFINITE</c>,
|
|
/// the exception <see cref="operation_timed_out Class">operation_timed_out</see> is thrown if the specified amount
|
|
/// of time expires before a message is received. If you want a zero length timeout, you should use the
|
|
/// <see cref="try_receive Function">try_receive</see> function, as opposed to calling <c>receive</c> with a timeout
|
|
/// of <c>0</c> (zero), as it is more efficient and does not throw exceptions on timeouts.
|
|
/// <para>For more information, see <see cref="Message Passing Functions"/>.</para>
|
|
/// </remarks>
|
|
/// <seealso cref="try_receive Function"/>
|
|
/// <seealso cref="send Function"/>
|
|
/// <seealso cref="asend Function"/>
|
|
/**/
|
|
template <class _Type>
|
|
_Type receive(ISource<_Type> &_Src, typename ITarget<_Type>::filter_method const& _Filter_proc, unsigned int _Timeout = COOPERATIVE_TIMEOUT_INFINITE)
|
|
{
|
|
return _Receive_impl(&_Src, _Timeout, &_Filter_proc);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper function that implements try_receive
|
|
/// A general try-receive implementation, allowing a context to look for data from
|
|
/// exactly one source and filter the values that are accepted. If the data is not
|
|
/// ready, try_receive will return false.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload type
|
|
/// </typeparam>
|
|
/// <param name="_Src">
|
|
/// A pointer to the source from which data is expected.
|
|
/// </param>
|
|
/// <param name="_value">
|
|
/// A reference to a location where the result will be placed.
|
|
/// </param>
|
|
/// <param name="_Filter_proc">
|
|
/// A pointer to a filter which will indicate whether to accept the data or not.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A bool indicating whether a payload was placed in <paramref name="_value"/> or not.
|
|
/// </returns>
|
|
/**/
|
|
template <class _Type>
|
|
bool _Try_receive_impl(ISource<_Type> * _Src, _Type & _value, typename ITarget<_Type>::filter_method const * _Filter_proc)
|
|
{
|
|
// The Immediate Recipient messaging block class is internal to the receive function
|
|
class _Immediate_recipient : public ITarget<_Type>
|
|
{
|
|
public:
|
|
// Create an Immediate Recipient
|
|
_Immediate_recipient(ISource<_Type> * _PSource) :
|
|
_M_pFilter(NULL), _M_pConnectedTo(NULL), _M_pMessage(NULL), _M_isInitialized(0)
|
|
{
|
|
_Connect(_PSource);
|
|
}
|
|
|
|
// Create an Immediate Recipient
|
|
_Immediate_recipient(ISource<_Type> * _PSource,
|
|
filter_method const& _Filter) :
|
|
_M_pFilter(NULL), _M_pConnectedTo(NULL), _M_pMessage(NULL), _M_isInitialized(0)
|
|
{
|
|
if (_Filter != NULL)
|
|
{
|
|
_M_pFilter = new filter_method(_Filter);
|
|
}
|
|
|
|
_Connect(_PSource);
|
|
}
|
|
|
|
// Cleans up any resources that may have been created by the ImmediateRecipient.
|
|
~_Immediate_recipient()
|
|
{
|
|
_Disconnect();
|
|
|
|
delete _M_pFilter;
|
|
delete _M_pMessage;
|
|
}
|
|
|
|
// Gets the value of the message sent to this ImmediateRecipient.
|
|
bool _Value(_Type & _value)
|
|
{
|
|
// Unlinking from our source guarantees that there are no threads in propagate
|
|
_Disconnect();
|
|
|
|
if (_M_pMessage != NULL)
|
|
{
|
|
_value = _M_pMessage->payload;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// The main propagation function for ITarget blocks. Called by a source
|
|
// block, generally within an asynchronous task to send messages to its targets.
|
|
virtual message_status propagate(message<_Type> * _PMessage, ISource<_Type> * _PSource)
|
|
{
|
|
message_status _Result = accepted;
|
|
|
|
// Throw exception if the message being propagated to this block is NULL
|
|
if (_PMessage == NULL)
|
|
{
|
|
throw std::invalid_argument("_PMessage");
|
|
}
|
|
|
|
if (_PSource == NULL)
|
|
{
|
|
throw std::invalid_argument("_PSource");
|
|
}
|
|
|
|
// Reject if the recipient has already received a message
|
|
if (_M_isInitialized == 1)
|
|
{
|
|
return declined;
|
|
}
|
|
|
|
// Reject if the message does not meet the filter requirements
|
|
if (_M_pFilter != NULL && !(*_M_pFilter)(_PMessage->payload))
|
|
{
|
|
return declined;
|
|
}
|
|
|
|
// Accept the message
|
|
_CONCRT_ASSERT(_PSource != NULL);
|
|
_M_pMessage = _PSource->accept(_PMessage->msg_id(), this);
|
|
|
|
// Set the initialized flag on this block
|
|
|
|
if (_M_pMessage != NULL)
|
|
{
|
|
// Fence to ensure that the above update to _M_pMessage is visible
|
|
_InterlockedExchange(&_M_isInitialized, 1);
|
|
_Result = accepted;
|
|
}
|
|
else
|
|
{
|
|
_Result = missed;
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
|
|
// Synchronously sends a message to this block. When this function completes the message will
|
|
// already have propagated into the block.
|
|
virtual message_status send(message<_Type> * _PMessage, ISource<_Type> * _PSource)
|
|
{
|
|
if (_PMessage == NULL)
|
|
{
|
|
throw std::invalid_argument("_PMessage");
|
|
}
|
|
|
|
if (_PSource == NULL)
|
|
{
|
|
throw std::invalid_argument("_PSource");
|
|
}
|
|
|
|
// Only the connected source is allowed to send messages
|
|
// to the blocking recepient. Decline messages without
|
|
// a source.
|
|
|
|
return declined;
|
|
}
|
|
|
|
private:
|
|
|
|
// Add a source messaging block
|
|
virtual void link_source(ISource<_Type> * _PSrc)
|
|
{
|
|
_M_pConnectedTo = _PSrc;
|
|
_PSrc->acquire_ref(this);
|
|
}
|
|
|
|
// Remove a source messaging block for this BlockingRecipient
|
|
virtual void unlink_source(ISource<_Type> * _PSource)
|
|
{
|
|
if (_InterlockedCompareExchangePointer(reinterpret_cast<void *volatile *>(&_M_pConnectedTo), (void *)NULL, _PSource) == _PSource)
|
|
{
|
|
_PSource->release_ref(this);
|
|
}
|
|
}
|
|
|
|
// Remove the source messaging block for this BlockingRecipient
|
|
virtual void unlink_sources()
|
|
{
|
|
ISource<_Type> * _PSource = reinterpret_cast<ISource<_Type> *>(_InterlockedExchangePointer(reinterpret_cast<void *volatile *>(&_M_pConnectedTo), (void *)NULL));
|
|
if (_PSource != NULL)
|
|
{
|
|
_PSource->unlink_target(this);
|
|
_PSource->release_ref(this);
|
|
}
|
|
}
|
|
|
|
// Connect to a source block
|
|
void _Connect(ISource<_Type> * _PSource)
|
|
{
|
|
if (_PSource == NULL)
|
|
{
|
|
throw std::invalid_argument("_PSource");
|
|
}
|
|
|
|
_CONCRT_ASSERT(_M_isInitialized == 0);
|
|
|
|
_PSource->link_target(this);
|
|
}
|
|
|
|
//
|
|
// Cleanup the connection to the trigger's source. There is no need
|
|
// to do anything about the associated context.
|
|
//
|
|
void _Disconnect()
|
|
{
|
|
unlink_sources();
|
|
}
|
|
|
|
// The source messaging block connected to this Recipient
|
|
ISource<_Type> * _M_pConnectedTo;
|
|
|
|
// The message that was received
|
|
message<_Type> * volatile _M_pMessage;
|
|
|
|
// A flag for whether or not this block has been initialized with a value
|
|
volatile long _M_isInitialized;
|
|
|
|
// The filter that is called on this block before accepting a message
|
|
filter_method * _M_pFilter;
|
|
};
|
|
|
|
if (_Filter_proc != NULL)
|
|
{
|
|
_Immediate_recipient _Recipient(_Src, *_Filter_proc);
|
|
return _Recipient._Value(_value);
|
|
}
|
|
else
|
|
{
|
|
_Immediate_recipient _Recipient(_Src);
|
|
return _Recipient._Value(_value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A general try-receive implementation, allowing a context to look for data from
|
|
/// exactly one source and filter the values that are accepted. If the data is not
|
|
/// ready, the method will return false.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload type.
|
|
/// </typeparam>
|
|
/// <param name="_Src">
|
|
/// A pointer or reference to the source from which data is expected.
|
|
/// </param>
|
|
/// <param name="_value">
|
|
/// A reference to a location where the result will be placed.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <c>bool</c> value indicating whether or not a payload was placed in <paramref name="_value"/>.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// For more information, see <see cref="Message Passing Functions"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="receive Function"/>
|
|
/// <seealso cref="send Function"/>
|
|
/// <seealso cref="asend Function"/>
|
|
/**/
|
|
template <class _Type>
|
|
bool try_receive(_Inout_ ISource<_Type> * _Src, _Type & _value)
|
|
{
|
|
return _Try_receive_impl(_Src, _value, NULL);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A general try-receive implementation, allowing a context to look for data from
|
|
/// exactly one source and filter the values that are accepted. If the data is not
|
|
/// ready, the method will return false.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload type.
|
|
/// </typeparam>
|
|
/// <param name="_Src">
|
|
/// A pointer or reference to the source from which data is expected.
|
|
/// </param>
|
|
/// <param name="_value">
|
|
/// A reference to a location where the result will be placed.
|
|
/// </param>
|
|
/// <param name="_Filter_proc">
|
|
/// A filter function which determines whether messages should be accepted.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <c>bool</c> value indicating whether or not a payload was placed in <paramref name="_value"/>.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// For more information, see <see cref="Message Passing Functions"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="receive Function"/>
|
|
/// <seealso cref="send Function"/>
|
|
/// <seealso cref="asend Function"/>
|
|
/**/
|
|
template <class _Type>
|
|
bool try_receive(_Inout_ ISource<_Type> * _Src, _Type & _value, typename ITarget<_Type>::filter_method const& _Filter_proc)
|
|
{
|
|
return _Try_receive_impl(_Src, _value, &_Filter_proc);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A general try-receive implementation, allowing a context to look for data from
|
|
/// exactly one source and filter the values that are accepted. If the data is not
|
|
/// ready, the method will return false.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload type
|
|
/// </typeparam>
|
|
/// <param name="_Src">
|
|
/// A pointer or reference to the source from which data is expected.
|
|
/// </param>
|
|
/// <param name="_value">
|
|
/// A reference to a location where the result will be placed.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <c>bool</c> value indicating whether or not a payload was placed in <paramref name="_value"/>.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// For more information, see <see cref="Message Passing Functions"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="receive Function"/>
|
|
/// <seealso cref="send Function"/>
|
|
/// <seealso cref="asend Function"/>
|
|
/**/
|
|
template <class _Type>
|
|
bool try_receive(ISource<_Type> & _Src, _Type & _value)
|
|
{
|
|
return _Try_receive_impl(&_Src, _value, NULL);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A general try-receive implementation, allowing a context to look for data from
|
|
/// exactly one source and filter the values that are accepted. If the data is not
|
|
/// ready, the method will return false.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload type
|
|
/// </typeparam>
|
|
/// <param name="_Src">
|
|
/// A pointer or reference to the source from which data is expected.
|
|
/// </param>
|
|
/// <param name="_value">
|
|
/// A reference to a location where the result will be placed.
|
|
/// </param>
|
|
/// <param name="_Filter_proc">
|
|
/// A filter function which determines whether messages should be accepted.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <c>bool</c> value indicating whether or not a payload was placed in <paramref name="_value"/>.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// For more information, see <see cref="Message Passing Functions"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="receive Function"/>
|
|
/// <seealso cref="send Function"/>
|
|
/// <seealso cref="asend Function"/>
|
|
/**/
|
|
template <class _Type>
|
|
bool try_receive(ISource<_Type> & _Src, _Type & _value, typename ITarget<_Type>::filter_method const& _Filter_proc)
|
|
{
|
|
return _Try_receive_impl(&_Src, _value, &_Filter_proc);
|
|
}
|
|
|
|
namespace details
|
|
{
|
|
//**************************************************************************
|
|
// Supporting blocks for send and asend
|
|
//**************************************************************************
|
|
|
|
// Originator block that pushes messages to a target
|
|
template <class _Type>
|
|
class _AnonymousOriginator : public ISource<_Type>
|
|
{
|
|
public:
|
|
|
|
typedef single_link_registry<ITarget<_Type>> _Target_registry;
|
|
|
|
// Create an Originator
|
|
_AnonymousOriginator() : _M_pMessage(NULL), _M_pTarget(NULL)
|
|
{
|
|
}
|
|
|
|
// Cleans up any resources that may have been created by the Originator.
|
|
virtual ~_AnonymousOriginator()
|
|
{
|
|
delete _M_pMessage;
|
|
}
|
|
|
|
// Removes a target messaging block for this Originator
|
|
virtual void unlink_target(ITarget<_Type> * _PTarget)
|
|
{
|
|
throw invalid_operation("unlink_target is not supported on _AnonymousOriginator");
|
|
}
|
|
|
|
// Removes the target messaging block from this Originator
|
|
virtual void unlink_targets()
|
|
{
|
|
throw invalid_operation("unlink_targets is not supported on _AnonymousOriginator");
|
|
}
|
|
|
|
// Accept on this Originator is called by a target to take ownership of a
|
|
// propagated message
|
|
virtual message<_Type> * accept(runtime_object_identity _MsgId, ITarget<_Type> * _PTarget)
|
|
{
|
|
if (_PTarget != _M_pTarget)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (_M_pMessage == NULL || _M_pMessage->msg_id() != _MsgId)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// The IDs match, actaully transfer ownership of the message and
|
|
// unlink away from the target
|
|
message<_Type> * _Result = _M_pMessage;
|
|
|
|
// The ownership of this message has changed. Set the internal pointer to NULL
|
|
// so it won't be deleted in the destructor
|
|
_M_pMessage = NULL;
|
|
|
|
return _Result;
|
|
}
|
|
|
|
// Reserve shall not be called by blocks that supports push
|
|
virtual bool reserve(runtime_object_identity _MsgId, ITarget<_Type> * _PTarget)
|
|
{
|
|
throw invalid_operation("reserve is not supported on _AnonymousOriginator");
|
|
}
|
|
|
|
// Consume shall not be called by blocks that supports push
|
|
virtual message<_Type> * consume(runtime_object_identity _MsgId, ITarget<_Type> * _PTarget)
|
|
{
|
|
throw invalid_operation("consume is not supported on _AnonymousOriginator");
|
|
}
|
|
|
|
// Release needs to be defined for ISource blocks, but Originator doesn't need to
|
|
// do anything for reservation release because there can only be one target block to read
|
|
// the data at a later time.
|
|
virtual void release(runtime_object_identity _MsgId, ITarget<_Type> * _PTarget)
|
|
{
|
|
throw invalid_operation("release is not supported on _AnonymousOriginator");
|
|
}
|
|
|
|
virtual void acquire_ref(_Inout_ ITarget<_Type> *)
|
|
{
|
|
throw invalid_operation("acquire_ref is not supported on _AnonymousOriginator");
|
|
}
|
|
|
|
virtual void release_ref(_Inout_ ITarget<_Type> *)
|
|
{
|
|
throw invalid_operation("release_ref is not supported on _AnonymousOriginator");
|
|
}
|
|
|
|
private:
|
|
friend class _Originator;
|
|
|
|
// Send the given value to the target
|
|
bool _internal_send(ITarget<_Type> * _PTarget, _Type const & _Value)
|
|
{
|
|
_M_pTarget = _PTarget;
|
|
|
|
_CONCRT_ASSERT(_M_pTarget != NULL);
|
|
_CONCRT_ASSERT(_M_pTarget->supports_anonymous_source());
|
|
|
|
// Create the message
|
|
message_status _Status = declined;
|
|
message<_Type> * _Msg = new message<_Type>(_Value);
|
|
|
|
_CONCRT_ASSERT(_M_pMessage == NULL);
|
|
_M_pMessage = _Msg;
|
|
|
|
// Send the message
|
|
_Status = _M_pTarget->send(_M_pMessage, this);
|
|
|
|
// If the message is declined, the destructor will
|
|
// delete the message
|
|
|
|
// status should not be postponed.
|
|
_CONCRT_ASSERT(_Status != postponed);
|
|
return (_Status == accepted);
|
|
}
|
|
|
|
bool _internal_asend(ITarget<_Type> * _PTarget, _Type const & _Value)
|
|
{
|
|
_M_pTarget = _PTarget;
|
|
|
|
_CONCRT_ASSERT(_M_pTarget != NULL);
|
|
_CONCRT_ASSERT(_M_pTarget->supports_anonymous_source());
|
|
|
|
// Create the message
|
|
message_status _Status = declined;
|
|
message<_Type> * _Msg = new message<_Type>(_Value);
|
|
|
|
_CONCRT_ASSERT(_M_pMessage == NULL);
|
|
_M_pMessage = _Msg;
|
|
|
|
// Send the message
|
|
_Status = _M_pTarget->propagate(_M_pMessage, this);
|
|
|
|
// If the message is declined, the destructor will
|
|
// delete the message
|
|
|
|
// status should not be postponed.
|
|
if (_Status == postponed)
|
|
{
|
|
throw invalid_operation("Messages offered by _AnonymousOriginator shall not be postponed");
|
|
}
|
|
|
|
return (_Status == accepted);
|
|
}
|
|
|
|
// Add a target messaging block for this Originator
|
|
virtual void link_target(ITarget<_Type> * _PTarget)
|
|
{
|
|
throw invalid_operation("link_target is not supported on _AnonymousOriginator");
|
|
}
|
|
|
|
// The message that will be propagated by the Originator
|
|
message<_Type> * _M_pMessage;
|
|
|
|
// The single target for this block
|
|
ITarget<_Type> * _M_pTarget;
|
|
};
|
|
|
|
// The Originator messaging block class is internal to the send function.
|
|
template <class _Type>
|
|
class _SyncOriginator : public ISource<_Type>
|
|
{
|
|
public:
|
|
|
|
typedef single_link_registry<ITarget<_Type>> _Target_registry;
|
|
|
|
// Create an Originator
|
|
_SyncOriginator() :
|
|
_M_pMessage(NULL),
|
|
_M_fStatus(postponed),
|
|
_M_referenceCount(0)
|
|
{
|
|
}
|
|
|
|
// Cleans up any resources that may have been created by the Originator.
|
|
virtual ~_SyncOriginator()
|
|
{
|
|
unlink_targets();
|
|
|
|
_Wait_on_ref();
|
|
|
|
delete _M_pMessage;
|
|
}
|
|
|
|
// Removes a target messaging block for this Originator
|
|
virtual void unlink_target(ITarget<_Type> * _PTarget)
|
|
{
|
|
if (_PTarget == NULL)
|
|
{
|
|
throw std::invalid_argument("_PTarget");
|
|
}
|
|
{
|
|
// Hold the lock to ensure that the target doesn't unlink while
|
|
// propagation is in progress.
|
|
_R_lock _Lock(_M_internalLock);
|
|
if (_M_connectedTargets.remove(_PTarget))
|
|
{
|
|
_Invoke_unlink_source(_PTarget);
|
|
|
|
// Indicate that the send is complete
|
|
_Done(declined);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Removes the target messaging block from this Originator
|
|
virtual void unlink_targets()
|
|
{
|
|
// Hold the lock to ensure that the target doesn't unlink while
|
|
// propagation is in progress.
|
|
_R_lock _Lock(_M_internalLock);
|
|
|
|
for (_Target_registry::iterator _Iter = _M_connectedTargets.begin(); *_Iter != NULL; ++_Iter)
|
|
{
|
|
ITarget<_Type> * _PTarget = *_Iter;
|
|
if (_M_connectedTargets.remove(_PTarget))
|
|
{
|
|
_Invoke_unlink_source(_PTarget);
|
|
}
|
|
}
|
|
|
|
// All targets should be unlinked
|
|
_CONCRT_ASSERT(_M_connectedTargets.count() == 0);
|
|
|
|
// Indicate that the send is complete
|
|
_Done(declined);
|
|
}
|
|
|
|
// Accept on this Originator is called by a target to take ownership of a
|
|
// propagated message
|
|
virtual message<_Type> * accept(runtime_object_identity _MsgId, ITarget<_Type> * _PTarget)
|
|
{
|
|
if (_PTarget == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (!_M_connectedTargets.contains(_PTarget))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (_M_pMessage == NULL || _M_pMessage->msg_id() != _MsgId)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// The IDs match, actaully transfer ownership of the message and
|
|
// unlink away from the target
|
|
message<_Type> * _Result = _M_pMessage;
|
|
|
|
// The ownership of this message has changed. Set the internal pointer to NULL
|
|
// so it won't be deleted in the destructor
|
|
_M_pMessage = NULL;
|
|
|
|
// The message has been accepted/consumed, propagate indication that it has succeeded
|
|
_Done(accepted);
|
|
|
|
return _Result;
|
|
}
|
|
|
|
// Reserve needs to be defined for ISource blocks, but Originator doesn't need to
|
|
// do anything for reservation because there can only be one target block to read
|
|
// the data at a later time.
|
|
virtual bool reserve(runtime_object_identity _MsgId, ITarget<_Type> * _PTarget)
|
|
{
|
|
if (_PTarget == NULL)
|
|
{
|
|
throw std::invalid_argument("_PTarget");
|
|
}
|
|
|
|
if (!_M_connectedTargets.contains(_PTarget))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (_M_pMessage->msg_id() != _MsgId)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Consume is called by a target messaging block to take ownership of a
|
|
// previously reserved message.
|
|
virtual message<_Type> * consume(runtime_object_identity _MsgId, ITarget<_Type> * _PTarget)
|
|
{
|
|
if (_PTarget == NULL)
|
|
{
|
|
throw std::invalid_argument("_PTarget");
|
|
}
|
|
|
|
if (!_M_connectedTargets.contains(_PTarget))
|
|
{
|
|
throw bad_target();
|
|
}
|
|
|
|
return accept(_MsgId, _PTarget);
|
|
}
|
|
|
|
// Release needs to be defined for ISource blocks, but Originator doesn't need to
|
|
// do anything for reservation release because there can only be one target block to read
|
|
// the data at a later time.
|
|
virtual void release(runtime_object_identity _MsgId, ITarget<_Type> * _PTarget)
|
|
{
|
|
if (_PTarget == NULL)
|
|
{
|
|
throw std::invalid_argument("_PTarget");
|
|
}
|
|
|
|
if (!_M_connectedTargets.contains(_PTarget))
|
|
{
|
|
throw bad_target();
|
|
}
|
|
|
|
if ((_M_pMessage == NULL) || (_M_pMessage->msg_id() != _MsgId))
|
|
{
|
|
throw message_not_found();
|
|
}
|
|
|
|
// If the previously reserved message is released, then propagate
|
|
// declined to indicate that the message was not accepted.
|
|
_Done(declined);
|
|
}
|
|
|
|
virtual void acquire_ref(_Inout_ ITarget<_Type> *)
|
|
{
|
|
_InterlockedIncrement(&_M_referenceCount);
|
|
}
|
|
|
|
virtual void release_ref(_Inout_ ITarget<_Type> *)
|
|
{
|
|
_InterlockedDecrement(&_M_referenceCount);
|
|
}
|
|
|
|
private:
|
|
|
|
friend class _Originator;
|
|
|
|
// Send the given value to the target
|
|
bool _internal_send(ITarget<_Type> * _PTarget, _Type const & _Value)
|
|
{
|
|
// _send should only be called once.
|
|
if (_PTarget == NULL)
|
|
{
|
|
throw std::invalid_argument("_PTarget");
|
|
}
|
|
|
|
message_status _Status = declined;
|
|
message<_Type> * _Msg = new message<_Type>(_Value);
|
|
|
|
{
|
|
// Hold the lock to ensure that the target doesn't unlink while
|
|
// propagation is in progress.
|
|
_R_lock _Lock(_M_internalLock);
|
|
|
|
// link to the target, create a message and send it
|
|
link_target(_PTarget);
|
|
|
|
_CONCRT_ASSERT(_M_pMessage == NULL);
|
|
_M_pMessage = _Msg;
|
|
|
|
// Send the message synchronously to the target
|
|
_Status = _PTarget->send(_M_pMessage, this);
|
|
}
|
|
|
|
if (_Status == postponed)
|
|
{
|
|
// If the target postponed the message, wait for it to
|
|
// be accepted/declined.
|
|
_Wait_for_completion();
|
|
|
|
// Procure the final status
|
|
_Status = _M_fStatus;
|
|
}
|
|
|
|
// status should not be postponed.
|
|
_CONCRT_ASSERT(_Status != postponed);
|
|
|
|
return (_Status == accepted);
|
|
}
|
|
|
|
// Add a target messaging block for this Originator
|
|
virtual void link_target(ITarget<_Type> * _PTarget)
|
|
{
|
|
if (_PTarget == NULL)
|
|
{
|
|
throw std::invalid_argument("_PTarget");
|
|
}
|
|
|
|
_M_connectedTargets.add(_PTarget);
|
|
_Invoke_link_source(_PTarget);
|
|
|
|
// There should be no pending messages to propagate at this time.
|
|
_CONCRT_ASSERT(_M_pMessage == NULL);
|
|
}
|
|
|
|
// Wait for the status to reach one of the terminal
|
|
// states (!= postponed)
|
|
void _Wait_for_completion()
|
|
{
|
|
// Wait for the event to be signalled
|
|
_M_ev.wait(COOPERATIVE_TIMEOUT_INFINITE);
|
|
_CONCRT_ASSERT(_M_fStatus != postponed);
|
|
|
|
}
|
|
|
|
void _Wait_on_ref()
|
|
{
|
|
::Concurrency::details::_SpinWaitBackoffNone spinWait;
|
|
while(_M_referenceCount != 0)
|
|
{
|
|
spinWait._SpinOnce();
|
|
}
|
|
}
|
|
|
|
// Indicate that the send operation has completed
|
|
void _Done(message_status _Status)
|
|
{
|
|
// postponed is not a done state
|
|
_CONCRT_ASSERT(_Status != postponed);
|
|
|
|
_M_fStatus = _Status;
|
|
_M_ev.set();
|
|
}
|
|
|
|
// The message that will be propagated by the Originator
|
|
message<_Type> * _M_pMessage;
|
|
|
|
// Event to indicate completion
|
|
event _M_ev;
|
|
|
|
// Final status of the send
|
|
volatile message_status _M_fStatus;
|
|
|
|
// A lock for modifying the buffer or the connected blocks
|
|
::Concurrency::details::_ReentrantPPLLock _M_internalLock;
|
|
|
|
// Connected targets
|
|
_Target_registry _M_connectedTargets;
|
|
|
|
volatile long _M_referenceCount;
|
|
};
|
|
|
|
// The Originator messaging block class is internal to the send function.
|
|
template <class _Type>
|
|
class _AsyncOriginator : public ISource<_Type>
|
|
{
|
|
public:
|
|
|
|
typedef single_link_registry<ITarget<_Type>> _Target_registry;
|
|
|
|
// Cleans up any resources that may have been created by the AsyncOriginator.
|
|
virtual ~_AsyncOriginator()
|
|
{
|
|
unlink_targets();
|
|
|
|
delete _M_pMessage;
|
|
}
|
|
|
|
// Removes a target messaging block for this AsyncOriginator
|
|
virtual void unlink_target(ITarget<_Type> * _PTarget)
|
|
{
|
|
if (_PTarget == NULL)
|
|
{
|
|
throw std::invalid_argument("_PTarget");
|
|
}
|
|
|
|
bool _Unlinked = false;
|
|
{
|
|
// Hold the lock to ensure that the target doesn't unlink while
|
|
// propagation is in progress.
|
|
_R_lock _Lock(_M_internalLock);
|
|
|
|
if (_M_connectedTargets.remove(_PTarget))
|
|
{
|
|
_Invoke_unlink_source(_PTarget);
|
|
_Unlinked = true;
|
|
}
|
|
}
|
|
|
|
// Release the lock before decrementing the refcount. Otherwise, the
|
|
// lock release could corrupt memory.
|
|
if (_Unlinked)
|
|
{
|
|
_Release_ref();
|
|
}
|
|
}
|
|
|
|
// Removes the target messaging block from this AsyncOriginator
|
|
virtual void unlink_targets()
|
|
{
|
|
bool _Unlinked = false;
|
|
{
|
|
// Hold the lock to ensure that the target doesn't unlink while
|
|
// propagation is in progress.
|
|
_R_lock _Lock(_M_internalLock);
|
|
|
|
for (_Target_registry::iterator _Iter = _M_connectedTargets.begin();
|
|
*_Iter != NULL;
|
|
++_Iter)
|
|
{
|
|
ITarget<_Type> * _PTarget = *_Iter;
|
|
if (_M_connectedTargets.remove(_PTarget))
|
|
{
|
|
_Invoke_unlink_source(_PTarget);
|
|
_Unlinked = true;
|
|
}
|
|
|
|
}
|
|
|
|
// All targets should be unlinked
|
|
_CONCRT_ASSERT(_M_connectedTargets.count() == 0);
|
|
}
|
|
|
|
// Release the lock before decrementing the refcount. Otherwise, the
|
|
// lock release could corrupt memory.
|
|
if (_Unlinked)
|
|
{
|
|
_Release_ref();
|
|
}
|
|
}
|
|
|
|
// Accept on this AsyncOriginator is called by a target to take ownership of a
|
|
// propagated message. This can only be called from propagate.
|
|
virtual message<_Type> * accept(runtime_object_identity _MsgId, ITarget<_Type> * _PTarget)
|
|
{
|
|
if (_PTarget == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (!_M_connectedTargets.contains(_PTarget))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (_M_pMessage == NULL || _M_pMessage->msg_id() != _MsgId)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// If the IDs match, actaully transfer ownership of the message.
|
|
//
|
|
message<_Type> * _Result = _M_pMessage;
|
|
_M_pMessage = NULL;
|
|
|
|
return _Result;
|
|
}
|
|
|
|
// Reserve needs to be defined for ISource blocks, but AsyncOriginator doesn't need to
|
|
// do anything for reservation because there can only be one target block to read
|
|
// the data at a later time.
|
|
virtual bool reserve(runtime_object_identity _MsgId, ITarget<_Type> * _PTarget)
|
|
{
|
|
if (_PTarget == NULL)
|
|
{
|
|
throw std::invalid_argument("_PTarget");
|
|
}
|
|
|
|
if (!_M_connectedTargets.contains(_PTarget))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (_M_pMessage == NULL || _M_pMessage->msg_id() != _MsgId)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Consume is called by a target messaging block to take ownership of a
|
|
// previously reserved message.
|
|
virtual message<_Type> * consume(runtime_object_identity _MsgId, ITarget<_Type> * _PTarget)
|
|
{
|
|
if (_PTarget == NULL)
|
|
{
|
|
throw std::invalid_argument("_PTarget");
|
|
}
|
|
|
|
if (!_M_connectedTargets.contains(_PTarget))
|
|
{
|
|
throw bad_target();
|
|
}
|
|
|
|
if (_M_pMessage == NULL || _M_pMessage->msg_id() != _MsgId)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// The ownership of this message has changed. Set the internal pointer to NULL
|
|
// so it won't be deleted in the destructor
|
|
|
|
message<_Type> * _Result = _M_pMessage;
|
|
_M_pMessage = NULL;
|
|
|
|
// We are done. Unlink from the target. DO NOT TOUCH "this" pointer after unlink
|
|
unlink_target(_PTarget);
|
|
|
|
return _Result;
|
|
}
|
|
|
|
// Release needs to be defined for ISource blocks, but AsyncOriginator doesn't need to
|
|
// do anything for reservation release because there can only be one target block to read
|
|
// the data at a later time.
|
|
virtual void release(runtime_object_identity _MsgId, ITarget<_Type> * _PTarget)
|
|
{
|
|
if (_PTarget == NULL)
|
|
{
|
|
throw std::invalid_argument("_PTarget");
|
|
}
|
|
|
|
if (!_M_connectedTargets.contains(_PTarget))
|
|
{
|
|
throw bad_target();
|
|
}
|
|
|
|
if ((_M_pMessage == NULL) || (_M_pMessage->msg_id() != _MsgId))
|
|
{
|
|
throw message_not_found();
|
|
}
|
|
|
|
// We can be connected to only 1 target. Unlink from the target.
|
|
// DO NOT TOUCH "this" pointer after unlink
|
|
unlink_target(_PTarget);
|
|
}
|
|
|
|
virtual void acquire_ref(_Inout_ ITarget<_Type> *)
|
|
{
|
|
_Acquire_ref();
|
|
}
|
|
|
|
virtual void release_ref(_Inout_ ITarget<_Type> *)
|
|
{
|
|
_Release_ref();
|
|
}
|
|
|
|
private:
|
|
|
|
friend class _Originator;
|
|
|
|
// Create an AsyncOriginator (constructor is private to ensure that
|
|
// it is allocated on the heap).
|
|
_AsyncOriginator() :
|
|
_M_pMessage(NULL),
|
|
_M_refcount(0)
|
|
{
|
|
}
|
|
|
|
// Send the given value to the target
|
|
bool _internal_send(ITarget<_Type> * _PTarget, _Type const & _Value)
|
|
{
|
|
// Keep a refcount so that this object doesn't get deleted if
|
|
// the target decides to unlink before we release our lock
|
|
_Acquire_ref();
|
|
|
|
message_status _Status = declined;
|
|
message<_Type> * _Msg = new message<_Type>(_Value);
|
|
|
|
{
|
|
// Hold the lock to ensure that the target doesn't unlink while
|
|
// propagation is in progress.
|
|
_R_lock _Lock(_M_internalLock);
|
|
|
|
// link to the target, create a message and send it
|
|
link_target(_PTarget);
|
|
|
|
_CONCRT_ASSERT(_M_pMessage == NULL);
|
|
_M_pMessage = _Msg;
|
|
|
|
_Status = _PTarget->propagate(_M_pMessage, this);
|
|
}
|
|
|
|
// If the status is anything other than postponed, unlink away
|
|
// from the target and delete the AsyncOriginator.
|
|
if (_Status != postponed)
|
|
{
|
|
unlink_target(_PTarget);
|
|
}
|
|
|
|
// Release the reference acquired above
|
|
_Release_ref();
|
|
|
|
return (_Status == accepted);
|
|
}
|
|
|
|
// Add a target messaging block for this AsyncOriginator
|
|
virtual void link_target(ITarget<_Type> * _PTarget)
|
|
{
|
|
if (_PTarget == NULL)
|
|
{
|
|
throw std::invalid_argument("_PTarget");
|
|
}
|
|
|
|
// Acquire a reference that will be released by unlink_target
|
|
_Acquire_ref();
|
|
_M_connectedTargets.add(_PTarget);
|
|
_Invoke_link_source(_PTarget);
|
|
|
|
// There should be no pending messages to propagate at this time.
|
|
_CONCRT_ASSERT(_M_pMessage == NULL);
|
|
|
|
}
|
|
|
|
// Acquire a reference on the async originator object
|
|
void _Acquire_ref()
|
|
{
|
|
_InterlockedIncrement(&_M_refcount);
|
|
}
|
|
|
|
// Release the reference on the async originator object. The object
|
|
// will be deleted when the reference count goes to 0.
|
|
void _Release_ref()
|
|
{
|
|
_CONCRT_ASSERT(_M_refcount > 0);
|
|
if (_InterlockedDecrement(&_M_refcount) == 0)
|
|
{
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
// The message that will be propagated by the AsyncOriginator
|
|
message<_Type> * _M_pMessage;
|
|
|
|
// Reference count to manage object lifetime
|
|
volatile long _M_refcount;
|
|
|
|
// The internal lock for this block for its message
|
|
::Concurrency::details::_ReentrantPPLLock _M_internalLock;
|
|
|
|
// connected targets
|
|
_Target_registry _M_connectedTargets;
|
|
};
|
|
|
|
// static class that exposes methods to initiate messages into
|
|
// a dataflow network
|
|
class _Originator
|
|
{
|
|
public:
|
|
|
|
// Synchronous initiation of messages
|
|
template <class _Type>
|
|
static bool _send(ITarget<_Type> * _Trg, const _Type& _Data)
|
|
{
|
|
if (_Trg != NULL && _Trg->supports_anonymous_source())
|
|
{
|
|
// _send will block until the message is accepted/rejected.
|
|
// Note that this invokes the send method on the target which
|
|
// would synchronously process the message.
|
|
_AnonymousOriginator<_Type> _Send_block;
|
|
return _Send_block._internal_send(_Trg, _Data);
|
|
}
|
|
else
|
|
{
|
|
// Create a blocking originator on the stack. _send will block until the
|
|
// message is accepted/rejected.
|
|
_SyncOriginator<_Type> _Orig;
|
|
return _Orig._internal_send(_Trg, _Data);
|
|
}
|
|
}
|
|
|
|
// Asynchronous initiation of messages
|
|
template <class _Type>
|
|
static bool _asend(ITarget<_Type> * _Trg, const _Type& _Data)
|
|
{
|
|
// If the block can participate in posting messages without requiring a call back, use that
|
|
// method of initiating the message rather for efficiency purposes.
|
|
if (_Trg != NULL && _Trg->supports_anonymous_source())
|
|
{
|
|
_AnonymousOriginator<_Type> _Asend_block;
|
|
return _Asend_block._internal_asend(_Trg, _Data);
|
|
}
|
|
else
|
|
{
|
|
// Needs to be allocated on the heap
|
|
_AsyncOriginator<_Type> * _AsyncOrig = new _AsyncOriginator<_Type>;
|
|
return _AsyncOrig->_internal_send(_Trg, _Data);
|
|
}
|
|
}
|
|
};
|
|
|
|
} // namespace details
|
|
|
|
/// <summary>
|
|
/// A synchronous send operation, which waits until the target either accepts or declines the message.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload type.
|
|
/// </typeparam>
|
|
/// <param name="_Trg">
|
|
/// A pointer or reference to the target to which data is sent.
|
|
/// </param>
|
|
/// <param name="_Data">
|
|
/// A reference to the data to be sent.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the message was accepted, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// For more information, see <see cref="Message Passing Functions"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="receive Function"/>
|
|
/// <seealso cref="try_receive Function"/>
|
|
/// <seealso cref="asend Function"/>
|
|
/**/
|
|
template <class _Type>
|
|
bool send(_Inout_ ITarget<_Type> * _Trg, const _Type& _Data)
|
|
{
|
|
return details::_Originator::_send(_Trg, _Data);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// A synchronous send operation, which waits until the target either accepts or declines the message.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The payload type.
|
|
/// </typeparam>
|
|
/// <param name="_Trg">
|
|
/// A pointer or reference to the target to which data is sent.
|
|
/// </param>
|
|
/// <param name="_Data">
|
|
/// A reference to the data to be sent.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the message was accepted, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// For more information, see <see cref="Message Passing Functions"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="receive Function"/>
|
|
/// <seealso cref="try_receive Function"/>
|
|
/// <seealso cref="asend Function"/>
|
|
/**/
|
|
template <class _Type>
|
|
bool send(ITarget<_Type> &_Trg, const _Type &_Data)
|
|
{
|
|
return send(&_Trg, _Data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// An asynchronous send operation, which schedules a task to propagate the data to the target block.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The type of the data to be sent.
|
|
/// </typeparam>
|
|
/// <param name="_Trg">
|
|
/// A pointer or reference to the target to which data is sent.
|
|
/// </param>
|
|
/// <param name="_Data">
|
|
/// A reference to the data to be sent.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the message was accepted before the method returned, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// For more information, see <see cref="Message Passing Functions"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="receive Function"/>
|
|
/// <seealso cref="try_receive Function"/>
|
|
/// <seealso cref="send Function"/>
|
|
/**/
|
|
template <class _Type>
|
|
bool asend(_Inout_ ITarget<_Type> * _Trg, const _Type& _Data)
|
|
{
|
|
return details::_Originator::_asend(_Trg, _Data);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// An asynchronous send operation, which schedules a task to propagate the value to the target block.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The type of the data to be sent.
|
|
/// </typeparam>
|
|
/// <param name="_Trg">
|
|
/// A pointer or reference to the target to which data is sent.
|
|
/// </param>
|
|
/// <param name="_Data">
|
|
/// A reference to the data to be sent.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the message was accepted, <c>false</c> otherwise.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// For more information, see <see cref="Message Passing Functions"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="receive Function"/>
|
|
/// <seealso cref="try_receive Function"/>
|
|
/// <seealso cref="send Function"/>
|
|
/**/
|
|
template <class _Type>
|
|
bool asend(ITarget<_Type> &_Trg, const _Type &_Data)
|
|
{
|
|
return asend(&_Trg, _Data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Associates the given name to the message block or agent in the ETW trace.
|
|
/// </summary>
|
|
/// <typeparam name="_Type">
|
|
/// The type of the object. This is typically a message block or an agent.
|
|
/// </typeparam>
|
|
/// <param name="_PObject">
|
|
/// A pointer to the message block or agent that is being named in the trace.
|
|
/// </param>
|
|
/// <param name="_Name">
|
|
/// The name for the given object.
|
|
/// </param>
|
|
template <class _Type>
|
|
void Trace_agents_register_name(_Inout_ _Type * _PObject, _In_z_ const wchar_t * _Name)
|
|
{
|
|
_Trace_agents(AGENTS_EVENT_NAME, ::Concurrency::details::_Trace_agents_get_id(_PObject), _Name);
|
|
}
|
|
|
|
} // namespace Concurrency
|
|
|
|
namespace concurrency = Concurrency;
|
|
|
|
#pragma warning(pop)
|
|
#pragma pack(pop)
|