前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Sweet Snippet 之 RingBuffer for UE4

Sweet Snippet 之 RingBuffer for UE4

作者头像
用户2615200
发布2021-09-10 10:50:30
5720
发布2021-09-10 10:50:30
举报
文章被收录于专栏:tkokof 的技术,小趣及杂念

本文简述了一种 环形缓冲区(ring buffer) 的实现方法

环形缓冲区(ring buffer)在一些情况下非常有用, UE4 中提供了一个默认的实现 TCircularBuffer(代码较短,下面直接贴出完整源码):

代码语言:javascript
复制
// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "CoreTypes.h"
#include "Misc/AssertionMacros.h"
#include "Containers/Array.h"
#include "Math/UnrealMathUtility.h"

/**
 * Template for circular buffers.
 *
 * The size of the buffer is rounded up to the next power of two in order speed up indexing
 * operations using a simple bit mask instead of the commonly used modulus operator that may
 * be slow on some platforms.
 */
template<typename ElementType> class TCircularBuffer
{
public:

	/**
	 * Creates and initializes a new instance of the TCircularBuffer class.
	 *
	 * @param Capacity The number of elements that the buffer can store (will be rounded up to the next power of 2).
	 */
	explicit TCircularBuffer(uint32 Capacity)
	{
		checkSlow(Capacity > 0);
		checkSlow(Capacity <= 0xffffffffU);

		Elements.AddZeroed(FMath::RoundUpToPowerOfTwo(Capacity));
		IndexMask = Elements.Num() - 1;
	}

	/**
	 * Creates and initializes a new instance of the TCircularBuffer class.
	 *
	 * @param Capacity The number of elements that the buffer can store (will be rounded up to the next power of 2).
	 * @param InitialValue The initial value for the buffer's elements.
	 */
	TCircularBuffer(uint32 Capacity, const ElementType& InitialValue)
	{
		checkSlow(Capacity <= 0xffffffffU);

		Elements.Init(InitialValue, FMath::RoundUpToPowerOfTwo(Capacity));
		IndexMask = Elements.Num() - 1;
	}

public:

	/**
	 * Returns the mutable element at the specified index.
	 *
	 * @param Index The index of the element to return.
	 */
	FORCEINLINE ElementType& operator[](uint32 Index)
	{
		return Elements[Index & IndexMask];
	}

	/**
	 * Returns the immutable element at the specified index.
	 *
	 * @param Index The index of the element to return.
	 */
	FORCEINLINE const ElementType& operator[](uint32 Index) const
	{
		return Elements[Index & IndexMask];
	}
	
public:

	/**
	 * Returns the number of elements that the buffer can hold.
	 *
	 * @return Buffer capacity.
	 */
	FORCEINLINE uint32 Capacity() const
	{
		return Elements.Num();
	}

	/**
	 * Calculates the index that follows the given index.
	 *
	 * @param CurrentIndex The current index.
	 * @return The next index.
	 */
	FORCEINLINE uint32 GetNextIndex(uint32 CurrentIndex) const
	{
		return ((CurrentIndex + 1) & IndexMask);
	}

	/**
	 * Calculates the index previous to the given index.
	 *
	 * @param CurrentIndex The current index.
	 * @return The previous index.
	 */
	FORCEINLINE uint32 GetPreviousIndex(uint32 CurrentIndex) const
	{
		return ((CurrentIndex - 1) & IndexMask);
	}

private:

	/** Holds the mask for indexing the buffer's elements. */
	uint32 IndexMask;

	/** Holds the buffer's elements. */
	TArray<ElementType> Elements;
};

实现上比较简单易懂,但同时在使用上也有不少局限,下面给出一个提供了更多功能的环形缓冲区实现(TRingBuffer),有兴趣的朋友可以看看:

代码语言:javascript
复制
// desc template ring buffer
// maintainer hugoyu

#pragma once

#include "CoreTypes.h"
#include "Misc/AssertionMacros.h"
#include "Containers/Array.h"
#include "Math/UnrealMathUtility.h"

enum class EKGRingBufferOverflowBehavior
{
	// the size of the ring buffer will double and elements will be copied over to the new buffer
	AutomaticExpansion,
	// log error
	LogError,
	// the new element is ignored
	DropElement,
	// overrides the element considered Head, but does not advance further. Additional overflows will override the same element, as long as it's not removed with UseHead()
	OverrideHead,
	// overrides the head and advances (the element that was 2nd now becomes the Head). Further overflows will keep overriding the element considered the current head
	OverrideHeadAndContinue,
};

template<typename ElementType>
class TKGRingBuffer
{
public:
	TKGRingBuffer(int initialSize = 32, EKGRingBufferOverflowBehavior overflowBehavior = EKGRingBufferOverflowBehavior::OverrideHeadAndContinue)
	{
		if (initialSize <= 0)
		{
			initialSize = 32;
		}

		m_overflowBehavior = overflowBehavior;
		m_elements.Reserve(initialSize);

		for (int i = 0; i < initialSize; ++i)
		{
			m_elements.Add(ElementType());
		}

		m_headIndex = 0;
		m_tailIndex = 0;
		m_usedElements = 0;
	}

	// adds the specified element as the Tail
	void Add(ElementType element)
	{
		if (m_usedElements == m_elements.Num())
		{
			// list is full
			switch (m_overflowBehavior)
			{
			case EKGRingBufferOverflowBehavior::AutomaticExpansion:
				Expand();
				return;
			case EKGRingBufferOverflowBehavior::LogError:
				UE_LOG(LogTemp, Error, TEXT("[RingBuffer]Ring buffer is full and expansion is not allowed"));
				return;
			case EKGRingBufferOverflowBehavior::DropElement:
				return;
			case EKGRingBufferOverflowBehavior::OverrideHead:
				m_elements[m_headIndex] = element;
				return;
			case EKGRingBufferOverflowBehavior::OverrideHeadAndContinue:
				m_headIndex = (m_headIndex + 1) % m_usedElements;
				m_elements[m_tailIndex] = element;
				m_tailIndex = (m_tailIndex + 1) % m_usedElements;
				return;
			}
		}
		else
		{
			// list is not full
			m_elements[m_tailIndex] = element;
			m_tailIndex = (m_tailIndex + 1) % m_elements.Num();
			++m_usedElements;
		}
	}

	// expands the backing store to allow more elements. Involves a Copy operation of previous elements
	void Expand()
	{
		int oldCount = m_elements.Num();
		int newCount = oldCount * 2;
		TArray<ElementType> newElemenets;
		newElemenets.Reserve(newCount);
		for (int i = 0; i < newCount; ++i)
		{
			newElemenets.Add(ElementType());
		}

		for (int i = 0; i < oldCount; ++i)
		{
			newElemenets[i] = GetValue(i);
		}

		m_headIndex = 0;
		m_tailIndex = oldCount;
		m_usedElements = oldCount;
		m_elements = newElemenets;
	}

	// gets the head element without removing it
	const ElementType& PeekHead() const
	{
		check(m_usedElements > 0);
		return m_elements[m_headIndex];
	}

	// returns the head and removes it, thus advancing the buffer
	ElementType PopHead()
	{
		check(m_usedElements > 0);

		ElementType head = m_elements[m_headIndex];

		if (m_headIndex == m_elements.Num() - 1)
		{
			m_headIndex = 0;
		}
		else
		{
			++m_headIndex;
		}

		--m_usedElements;

		return head;
	}

	// gets the number of currently used elements
	int Count() const
	{
		return m_usedElements;
	}

	// gets the capacity of the ring buffer
	int Capacity() const
	{
		return m_elements.Num();
	}

	// get value by logic index
	ElementType& GetValue(int logicIndex)
	{
		check(logicIndex >= 0 && logicIndex < m_usedElements);

		int realIndex = (m_headIndex + logicIndex) % m_elements.Num();
		return m_elements[realIndex];
	}

	// get value by logic index const
	const ElementType& GetValue(int logicIndex) const
	{
		check(logicIndex >= 0 && logicIndex < m_usedElements);
		
		int realIndex = (m_headIndex + logicIndex) % m_elements.Num();
		return m_elements[realIndex];
	}

	// get value be logic index
	ElementType& operator[](int logicIndex)
	{
		return GetValue(logicIndex);
	}

	// get value be logic index const
	const ElementType& operator[](int logicIndex) const
	{
		return GetValue(logicIndex);
	}

	// convert to TArray
	TArray<ElementType> ToArray() const
	{
		TArray<ElementType> arrayBuffer;

		for (int i = m_headIndex, j = 0; j < m_usedElements; i = (i + 1) % m_elements.Num(), ++j)
		{
			arrayBuffer.Add(m_elements[i]);
		}

		return arrayBuffer;
	}

protected:
	int m_headIndex{ 0 };
	int m_tailIndex{ 0 };
	int m_usedElements{ 0 };
	EKGRingBufferOverflowBehavior m_overflowBehavior{ EKGRingBufferOverflowBehavior::OverrideHeadAndContinue };

	TArray<ElementType> m_elements;
};
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/08/10 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档