various: (fixes #2668) Make template classes use NS_LOG_* macros
This commit is contained in:
@@ -342,6 +342,60 @@ Adding logging to your code is very simple:
|
||||
|
||||
2. Add logging statements (macro calls) to your functions and function bodies.
|
||||
|
||||
In case you want to add logging statements to the methods of your template class
|
||||
(which are defined in an header file):
|
||||
|
||||
1. Invoke the ``NS_LOG_TEMPLATE_DECLARE;`` macro in the private section of
|
||||
your class declaration. For instance:
|
||||
|
||||
::
|
||||
|
||||
template <typename Item>
|
||||
class Queue : public QueueBase
|
||||
{
|
||||
...
|
||||
private:
|
||||
std::list<Ptr<Item> > m_packets; //!< the items in the queue
|
||||
NS_LOG_TEMPLATE_DECLARE; //!< the log component
|
||||
};
|
||||
|
||||
This requires you to perform these steps for all the subclasses of your class.
|
||||
|
||||
2. Invoke the ``NS_LOG_TEMPLATE_DEFINE (...);`` macro in the constructor of
|
||||
your class by providing the name of a log component registered by calling
|
||||
the ``NS_LOG_COMPONENT_DEFINE (...);`` macro in some module. For instance:
|
||||
|
||||
::
|
||||
|
||||
template <typename Item>
|
||||
Queue<Item>::Queue ()
|
||||
: NS_LOG_TEMPLATE_DEFINE ("Queue")
|
||||
{
|
||||
}
|
||||
|
||||
3. Add logging statements (macro calls) to the methods of your class.
|
||||
|
||||
In case you want to add logging statements to a static member template
|
||||
(which is defined in an header file):
|
||||
|
||||
1. Invoke the ``NS_LOG_STATIC_TEMPLATE_DEFINE (...);`` macro in your static
|
||||
method by providing the name of a log component registered by calling
|
||||
the ``NS_LOG_COMPONENT_DEFINE (...);`` macro in some module. For instance:
|
||||
|
||||
::
|
||||
|
||||
template <typename Item>
|
||||
void
|
||||
NetDeviceQueue::PacketEnqueued (Ptr<Queue<Item> > queue,
|
||||
Ptr<NetDeviceQueueInterface> ndqi,
|
||||
uint8_t txq, Ptr<const Item> item)
|
||||
{
|
||||
|
||||
NS_LOG_STATIC_TEMPLATE_DEFINE ("NetDeviceQueueInterface");
|
||||
...
|
||||
|
||||
2. Add logging statements (macro calls) to your static method.
|
||||
|
||||
Controlling timestamp precision
|
||||
*******************************
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <utility>
|
||||
#include <iostream>
|
||||
#include "assert.h"
|
||||
#include <stdexcept>
|
||||
#include "ns3/core-config.h"
|
||||
#include "fatal-error.h"
|
||||
|
||||
@@ -130,6 +131,23 @@ LogComponent::LogComponent (const std::string & name,
|
||||
components->insert (std::make_pair (name, this));
|
||||
}
|
||||
|
||||
LogComponent &
|
||||
GetLogComponent (const std::string name)
|
||||
{
|
||||
LogComponent::ComponentList *components = LogComponent::GetComponentList ();
|
||||
LogComponent* ret;
|
||||
|
||||
try
|
||||
{
|
||||
ret = components->at (name);
|
||||
}
|
||||
catch (std::out_of_range&)
|
||||
{
|
||||
NS_FATAL_ERROR ("Log component \"" << name << "\" does not exist.");
|
||||
}
|
||||
return *ret;
|
||||
}
|
||||
|
||||
void
|
||||
LogComponent::EnvVarCheck (void)
|
||||
{
|
||||
|
||||
@@ -212,6 +212,39 @@ void LogComponentDisableAll (enum LogLevel level);
|
||||
#define NS_LOG_COMPONENT_DEFINE_MASK(name, mask) \
|
||||
static ns3::LogComponent g_log = ns3::LogComponent (name, __FILE__, mask)
|
||||
|
||||
/**
|
||||
* Declare a reference to a Log component.
|
||||
*
|
||||
* This macro should be used in the declaration of template classes
|
||||
* to allow their methods (defined in an header file) to make use of
|
||||
* the NS_LOG_* macros. This macro should be used in the private
|
||||
* section to prevent subclasses from using the same log component
|
||||
* as the base class.
|
||||
*/
|
||||
#define NS_LOG_TEMPLATE_DECLARE LogComponent & g_log
|
||||
|
||||
/**
|
||||
* Initialize a reference to a Log component.
|
||||
*
|
||||
* This macro should be used in the constructor of template classes
|
||||
* to allow their methods (defined in an header file) to make use of
|
||||
* the NS_LOG_* macros.
|
||||
*
|
||||
* \param [in] name The log component name.
|
||||
*/
|
||||
#define NS_LOG_TEMPLATE_DEFINE(name) g_log (GetLogComponent (name))
|
||||
|
||||
/**
|
||||
* Declare and initialize a reference to a Log component.
|
||||
*
|
||||
* This macro should be used in static template methods to allow their
|
||||
* methods (defined in an header file) to make use of the NS_LOG_* macros.
|
||||
*
|
||||
* \param [in] name The log component name.
|
||||
*/
|
||||
#define NS_LOG_STATIC_TEMPLATE_DEFINE(name) \
|
||||
static LogComponent & g_log = GetLogComponent (name)
|
||||
|
||||
/**
|
||||
* Use \ref NS_LOG to output a message of level LOG_ERROR.
|
||||
*
|
||||
@@ -409,7 +442,14 @@ private:
|
||||
|
||||
}; // class LogComponent
|
||||
|
||||
|
||||
/**
|
||||
* Get the LogComponent registered with the given name.
|
||||
*
|
||||
* \param [in] name The name of the LogComponent.
|
||||
* \return a reference to the requested LogComponent
|
||||
*/
|
||||
LogComponent & GetLogComponent (const std::string name);
|
||||
|
||||
/**
|
||||
* Insert `, ` when streaming function arguments.
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
|
||||
namespace ns3 {
|
||||
|
||||
NS_LOG_COMPONENT_DEFINE ("DropTailQueue");
|
||||
|
||||
NS_OBJECT_TEMPLATE_CLASS_DEFINE (DropTailQueue,Packet);
|
||||
|
||||
} // namespace ns3
|
||||
|
||||
@@ -58,6 +58,8 @@ private:
|
||||
using Queue<Item>::DoDequeue;
|
||||
using Queue<Item>::DoRemove;
|
||||
using Queue<Item>::DoPeek;
|
||||
|
||||
NS_LOG_TEMPLATE_DECLARE; //!< redefinition of the log component
|
||||
};
|
||||
|
||||
|
||||
@@ -79,22 +81,23 @@ DropTailQueue<Item>::GetTypeId (void)
|
||||
|
||||
template <typename Item>
|
||||
DropTailQueue<Item>::DropTailQueue () :
|
||||
Queue<Item> ()
|
||||
Queue<Item> (),
|
||||
NS_LOG_TEMPLATE_DEFINE ("DropTailQueue")
|
||||
{
|
||||
QUEUE_LOG (LOG_LOGIC, "DropTailQueue(" << this << ")");
|
||||
NS_LOG_FUNCTION (this);
|
||||
}
|
||||
|
||||
template <typename Item>
|
||||
DropTailQueue<Item>::~DropTailQueue ()
|
||||
{
|
||||
QUEUE_LOG (LOG_LOGIC, "~DropTailQueue(" << this << ")");
|
||||
NS_LOG_FUNCTION (this);
|
||||
}
|
||||
|
||||
template <typename Item>
|
||||
bool
|
||||
DropTailQueue<Item>::Enqueue (Ptr<Item> item)
|
||||
{
|
||||
QUEUE_LOG (LOG_LOGIC, "DropTailQueue:Enqueue(" << this << ", " << item << ")");
|
||||
NS_LOG_FUNCTION (this << item);
|
||||
|
||||
return DoEnqueue (Tail (), item);
|
||||
}
|
||||
@@ -103,11 +106,11 @@ template <typename Item>
|
||||
Ptr<Item>
|
||||
DropTailQueue<Item>::Dequeue (void)
|
||||
{
|
||||
QUEUE_LOG (LOG_LOGIC, "DropTailQueue:Dequeue(" << this << ")");
|
||||
NS_LOG_FUNCTION (this);
|
||||
|
||||
Ptr<Item> item = DoDequeue (Head ());
|
||||
|
||||
QUEUE_LOG (LOG_LOGIC, "Popped " << item);
|
||||
NS_LOG_LOGIC ("Popped " << item);
|
||||
|
||||
return item;
|
||||
}
|
||||
@@ -116,11 +119,11 @@ template <typename Item>
|
||||
Ptr<Item>
|
||||
DropTailQueue<Item>::Remove (void)
|
||||
{
|
||||
QUEUE_LOG (LOG_LOGIC, "DropTailQueue:Remove(" << this << ")");
|
||||
NS_LOG_FUNCTION (this);
|
||||
|
||||
Ptr<Item> item = DoRemove (Head ());
|
||||
|
||||
QUEUE_LOG (LOG_LOGIC, "Removed " << item);
|
||||
NS_LOG_LOGIC ("Removed " << item);
|
||||
|
||||
return item;
|
||||
}
|
||||
@@ -129,7 +132,7 @@ template <typename Item>
|
||||
Ptr<const Item>
|
||||
DropTailQueue<Item>::Peek (void) const
|
||||
{
|
||||
QUEUE_LOG (LOG_LOGIC, "DropTailQueue:Peek(" << this << ")");
|
||||
NS_LOG_FUNCTION (this);
|
||||
|
||||
return DoPeek (Head ());
|
||||
}
|
||||
|
||||
@@ -145,12 +145,6 @@ NetDeviceQueue::GetQueueLimits ()
|
||||
return m_queueLimits;
|
||||
}
|
||||
|
||||
void
|
||||
NetDeviceQueue::DoNsLog (const enum LogLevel level, std::string str)
|
||||
{
|
||||
NS_LOG (level, str);
|
||||
}
|
||||
|
||||
|
||||
NS_OBJECT_ENSURE_REGISTERED (NetDeviceQueueInterface);
|
||||
|
||||
|
||||
@@ -193,14 +193,6 @@ public:
|
||||
uint8_t txq, Ptr<const Item> item);
|
||||
|
||||
private:
|
||||
/**
|
||||
* \brief Pass messages to the ns-3 logging system
|
||||
*
|
||||
* \param level the log level
|
||||
* \param str the message to log
|
||||
*/
|
||||
static void DoNsLog (const enum LogLevel level, std::string str);
|
||||
|
||||
bool m_stoppedByDevice; //!< True if the queue has been stopped by the device
|
||||
bool m_stoppedByQueueLimits; //!< True if the queue has been stopped by a queue limits object
|
||||
Ptr<QueueLimits> m_queueLimits; //!< Queue limits object
|
||||
@@ -338,14 +330,6 @@ private:
|
||||
};
|
||||
|
||||
|
||||
#define NDQI_LOG(level,params) \
|
||||
{ \
|
||||
std::stringstream ss; \
|
||||
ss << params; \
|
||||
DoNsLog (level, ss.str ()); \
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of the templates declared above.
|
||||
*/
|
||||
@@ -374,8 +358,9 @@ NetDeviceQueue::PacketEnqueued (Ptr<Queue<Item> > queue,
|
||||
Ptr<NetDeviceQueueInterface> ndqi,
|
||||
uint8_t txq, Ptr<const Item> item)
|
||||
{
|
||||
NDQI_LOG (LOG_LOGIC, "NetDeviceQueue:PacketEnqueued(" << queue << ", " << ndqi
|
||||
<< ", " << txq << ", " << item << ")");
|
||||
NS_LOG_STATIC_TEMPLATE_DEFINE ("NetDeviceQueueInterface");
|
||||
|
||||
NS_LOG_FUNCTION (queue << ndqi << txq << item);
|
||||
|
||||
// Inform BQL
|
||||
ndqi->GetTxQueue (txq)->NotifyQueuedBytes (item->GetSize ());
|
||||
@@ -390,8 +375,8 @@ NetDeviceQueue::PacketEnqueued (Ptr<Queue<Item> > queue,
|
||||
(queue->GetMode () == QueueBase::QUEUE_MODE_BYTES &&
|
||||
queue->GetNBytes () + mtu > queue->GetMaxBytes ()))
|
||||
{
|
||||
NDQI_LOG (LOG_DEBUG, "The device queue is being stopped (" << queue->GetNPackets ()
|
||||
<< " packets and " << queue->GetNBytes () << " bytes inside)");
|
||||
NS_LOG_DEBUG ("The device queue is being stopped (" << queue->GetNPackets ()
|
||||
<< " packets and " << queue->GetNBytes () << " bytes inside)");
|
||||
ndqi->GetTxQueue (txq)->Stop ();
|
||||
}
|
||||
}
|
||||
@@ -402,8 +387,9 @@ NetDeviceQueue::PacketDequeued (Ptr<Queue<Item> > queue,
|
||||
Ptr<NetDeviceQueueInterface> ndqi,
|
||||
uint8_t txq, Ptr<const Item> item)
|
||||
{
|
||||
NDQI_LOG (LOG_LOGIC, "NetDeviceQueue:PacketDequeued(" << queue << ", " << ndqi
|
||||
<< ", " << txq << ", " << item << ")");
|
||||
NS_LOG_STATIC_TEMPLATE_DEFINE ("NetDeviceQueueInterface");
|
||||
|
||||
NS_LOG_FUNCTION (queue << ndqi << txq << item);
|
||||
|
||||
// Inform BQL
|
||||
ndqi->GetTxQueue (txq)->NotifyTransmittedBytes (item->GetSize ());
|
||||
@@ -429,16 +415,17 @@ NetDeviceQueue::PacketDiscarded (Ptr<Queue<Item> > queue,
|
||||
Ptr<NetDeviceQueueInterface> ndqi,
|
||||
uint8_t txq, Ptr<const Item> item)
|
||||
{
|
||||
NDQI_LOG (LOG_LOGIC, "NetDeviceQueue:PacketDiscarded(" << queue << ", " << ndqi
|
||||
<< ", " << txq << ", " << item << ")");
|
||||
NS_LOG_STATIC_TEMPLATE_DEFINE ("NetDeviceQueueInterface");
|
||||
|
||||
NS_LOG_FUNCTION (queue << ndqi << txq << item);
|
||||
|
||||
// This method is called when a packet is discarded before being enqueued in the
|
||||
// device queue, likely because the queue is full. This should not happen if the
|
||||
// device correctly stops the queue. Anyway, stop the tx queue, so that the upper
|
||||
// layers do not send packets until there is room in the queue again.
|
||||
|
||||
NDQI_LOG (LOG_ERROR, "BUG! No room in the device queue for the received packet! ("
|
||||
<< queue->GetNPackets () << " packets and " << queue->GetNBytes () << " bytes inside)");
|
||||
NS_LOG_ERROR ("BUG! No room in the device queue for the received packet! ("
|
||||
<< queue->GetNPackets () << " packets and " << queue->GetNBytes () << " bytes inside)");
|
||||
|
||||
ndqi->GetTxQueue (txq)->Stop ();
|
||||
}
|
||||
|
||||
@@ -266,10 +266,4 @@ QueueBase::GetMaxBytes (void) const
|
||||
return m_maxBytes;
|
||||
}
|
||||
|
||||
void
|
||||
QueueBase::DoNsLog (const enum LogLevel level, std::string str) const
|
||||
{
|
||||
NS_LOG (level, str);
|
||||
}
|
||||
|
||||
} // namespace ns3
|
||||
|
||||
@@ -229,15 +229,6 @@ public:
|
||||
double GetDroppedPacketsPerSecondVariance (void);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/**
|
||||
* \brief Actually pass messages to the ns-3 logging system
|
||||
*
|
||||
* \param level the log level
|
||||
* \param str the message to log
|
||||
*/
|
||||
void DoNsLog (const enum LogLevel level, std::string str) const;
|
||||
|
||||
private:
|
||||
TracedValue<uint32_t> m_nBytes; //!< Number of bytes in the queue
|
||||
uint32_t m_nTotalReceivedBytes; //!< Total received bytes
|
||||
@@ -417,6 +408,7 @@ protected:
|
||||
|
||||
private:
|
||||
std::list<Ptr<Item> > m_packets; //!< the items in the queue
|
||||
NS_LOG_TEMPLATE_DECLARE; //!< the log component
|
||||
|
||||
/// Traced callback: fired when a packet is enqueued
|
||||
TracedCallback<Ptr<const Item> > m_traceEnqueue;
|
||||
@@ -431,14 +423,6 @@ private:
|
||||
};
|
||||
|
||||
|
||||
#define QUEUE_LOG(level,params) \
|
||||
{ \
|
||||
std::stringstream ss; \
|
||||
ss << params; \
|
||||
QueueBase::DoNsLog (level, ss.str ()); \
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of the templates declared above.
|
||||
*/
|
||||
@@ -472,6 +456,7 @@ Queue<Item>::GetTypeId (void)
|
||||
|
||||
template <typename Item>
|
||||
Queue<Item>::Queue ()
|
||||
: NS_LOG_TEMPLATE_DEFINE ("Queue")
|
||||
{
|
||||
}
|
||||
|
||||
@@ -484,18 +469,18 @@ template <typename Item>
|
||||
bool
|
||||
Queue<Item>::DoEnqueue (ConstIterator pos, Ptr<Item> item)
|
||||
{
|
||||
QUEUE_LOG (LOG_LOGIC, "Queue:DoEnqueue(" << this << ", " << item << ")");
|
||||
NS_LOG_FUNCTION (this << item);
|
||||
|
||||
if (m_mode == QUEUE_MODE_PACKETS && (m_nPackets.Get () >= m_maxPackets))
|
||||
{
|
||||
QUEUE_LOG (LOG_LOGIC, "Queue full (at max packets) -- dropping pkt");
|
||||
NS_LOG_LOGIC ("Queue full (at max packets) -- dropping pkt");
|
||||
DropBeforeEnqueue (item);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_mode == QUEUE_MODE_BYTES && (m_nBytes.Get () + item->GetSize () > m_maxBytes))
|
||||
{
|
||||
QUEUE_LOG (LOG_LOGIC, "Queue full (packet would exceed max bytes) -- dropping pkt");
|
||||
NS_LOG_LOGIC ("Queue full (packet would exceed max bytes) -- dropping pkt");
|
||||
DropBeforeEnqueue (item);
|
||||
return false;
|
||||
}
|
||||
@@ -509,7 +494,7 @@ Queue<Item>::DoEnqueue (ConstIterator pos, Ptr<Item> item)
|
||||
m_nPackets++;
|
||||
m_nTotalReceivedPackets++;
|
||||
|
||||
QUEUE_LOG (LOG_LOGIC, "m_traceEnqueue (p)");
|
||||
NS_LOG_LOGIC ("m_traceEnqueue (p)");
|
||||
m_traceEnqueue (item);
|
||||
|
||||
return true;
|
||||
@@ -519,11 +504,11 @@ template <typename Item>
|
||||
Ptr<Item>
|
||||
Queue<Item>::DoDequeue (ConstIterator pos)
|
||||
{
|
||||
QUEUE_LOG (LOG_LOGIC, "Queue:DoDequeue(" << this << ")");
|
||||
NS_LOG_FUNCTION (this);
|
||||
|
||||
if (m_nPackets.Get () == 0)
|
||||
{
|
||||
QUEUE_LOG (LOG_LOGIC, "Queue empty");
|
||||
NS_LOG_LOGIC ("Queue empty");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -538,7 +523,7 @@ Queue<Item>::DoDequeue (ConstIterator pos)
|
||||
m_nBytes -= item->GetSize ();
|
||||
m_nPackets--;
|
||||
|
||||
QUEUE_LOG (LOG_LOGIC, "m_traceDequeue (p)");
|
||||
NS_LOG_LOGIC ("m_traceDequeue (p)");
|
||||
m_traceDequeue (item);
|
||||
}
|
||||
return item;
|
||||
@@ -548,11 +533,11 @@ template <typename Item>
|
||||
Ptr<Item>
|
||||
Queue<Item>::DoRemove (ConstIterator pos)
|
||||
{
|
||||
QUEUE_LOG (LOG_LOGIC, "Queue:DoRemove(" << this << ")");
|
||||
NS_LOG_FUNCTION (this);
|
||||
|
||||
if (m_nPackets.Get () == 0)
|
||||
{
|
||||
QUEUE_LOG (LOG_LOGIC, "Queue empty");
|
||||
NS_LOG_LOGIC ("Queue empty");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -576,7 +561,7 @@ template <typename Item>
|
||||
void
|
||||
Queue<Item>::Flush (void)
|
||||
{
|
||||
QUEUE_LOG (LOG_LOGIC, "Queue:Flush(" << this << ")");
|
||||
NS_LOG_FUNCTION (this);
|
||||
while (!IsEmpty ())
|
||||
{
|
||||
Remove ();
|
||||
@@ -587,11 +572,11 @@ template <typename Item>
|
||||
Ptr<const Item>
|
||||
Queue<Item>::DoPeek (ConstIterator pos) const
|
||||
{
|
||||
QUEUE_LOG (LOG_LOGIC, "Queue:DoPeek(" << this << ")");
|
||||
NS_LOG_FUNCTION (this);
|
||||
|
||||
if (m_nPackets.Get () == 0)
|
||||
{
|
||||
QUEUE_LOG (LOG_LOGIC, "Queue empty");
|
||||
NS_LOG_LOGIC ("Queue empty");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -614,14 +599,14 @@ template <typename Item>
|
||||
void
|
||||
Queue<Item>::DropBeforeEnqueue (Ptr<Item> item)
|
||||
{
|
||||
QUEUE_LOG (LOG_LOGIC, "Queue:DropBeforeEnqueue(" << this << ", " << item << ")");
|
||||
NS_LOG_FUNCTION (this << item);
|
||||
|
||||
m_nTotalDroppedPackets++;
|
||||
m_nTotalDroppedPacketsBeforeEnqueue++;
|
||||
m_nTotalDroppedBytes += item->GetSize ();
|
||||
m_nTotalDroppedBytesBeforeEnqueue += item->GetSize ();
|
||||
|
||||
QUEUE_LOG (LOG_LOGIC, "m_traceDropBeforeEnqueue (p)");
|
||||
NS_LOG_LOGIC ("m_traceDropBeforeEnqueue (p)");
|
||||
m_traceDrop (item);
|
||||
m_traceDropBeforeEnqueue (item);
|
||||
}
|
||||
@@ -630,14 +615,14 @@ template <typename Item>
|
||||
void
|
||||
Queue<Item>::DropAfterDequeue (Ptr<Item> item)
|
||||
{
|
||||
QUEUE_LOG (LOG_LOGIC, "Queue:DropAfterDequeue(" << this << ", " << item << ")");
|
||||
NS_LOG_FUNCTION (this << item);
|
||||
|
||||
m_nTotalDroppedPackets++;
|
||||
m_nTotalDroppedPacketsAfterDequeue++;
|
||||
m_nTotalDroppedBytes += item->GetSize ();
|
||||
m_nTotalDroppedBytesAfterDequeue += item->GetSize ();
|
||||
|
||||
QUEUE_LOG (LOG_LOGIC, "m_traceDropAfterDequeue (p)");
|
||||
NS_LOG_LOGIC ("m_traceDropAfterDequeue (p)");
|
||||
m_traceDrop (item);
|
||||
m_traceDropAfterDequeue (item);
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ WifiMacQueue::GetTypeId (void)
|
||||
|
||||
template<>
|
||||
WifiMacQueue::WifiQueue ()
|
||||
: NS_LOG_TEMPLATE_DEFINE ("WifiMacQueue")
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -319,6 +319,8 @@ private:
|
||||
|
||||
Time m_maxDelay; //!< Time to live for packets in the queue
|
||||
DropPolicy m_dropPolicy; //!< Drop behavior of queue
|
||||
|
||||
NS_LOG_TEMPLATE_DECLARE; //!< redefinition of the log component
|
||||
};
|
||||
|
||||
/// Forward declare overridden methods to avoid specializing after instantiation
|
||||
|
||||
Reference in New Issue
Block a user