فهرست منبع

Allowing buffers to be empty.

David Piuva 2 سال پیش
والد
کامیت
5e697bc6b9
2فایلهای تغییر یافته به همراه52 افزوده شده و 22 حذف شده
  1. 21 13
      Source/DFPSR/api/bufferAPI.cpp
  2. 31 9
      Source/DFPSR/api/bufferAPI.h

+ 21 - 13
Source/DFPSR/api/bufferAPI.cpp

@@ -39,7 +39,11 @@ public:
 	uint8_t *data;
 	std::function<void(uint8_t *)> destructor;
 public:
+	// Create head without data.
+	BufferImpl();
+	// Create head with newly allocated data.
 	explicit BufferImpl(int64_t newSize);
+	// Create head with inherited data.
 	BufferImpl(int64_t newSize, uint8_t *newData);
 	~BufferImpl();
 public:
@@ -74,6 +78,8 @@ static const int buffer_alignment = 16;
 	}
 #endif
 
+BufferImpl::BufferImpl() : size(0), bufferSize(0), data(nullptr) {}
+
 BufferImpl::BufferImpl(int64_t newSize) :
   size(newSize),
   bufferSize(roundUp(newSize, buffer_alignment)) {
@@ -88,7 +94,9 @@ BufferImpl::BufferImpl(int64_t newSize, uint8_t *newData)
 : size(newSize), bufferSize(newSize), data(newData), destructor([](uint8_t *data) { free(data); }) {}
 
 BufferImpl::~BufferImpl() {
-	this->destructor(this->data);
+	if (this->data) {
+		this->destructor(this->data);
+	}
 }
 
 // API
@@ -98,33 +106,33 @@ Buffer buffer_clone(const Buffer &buffer) {
 		return Buffer();
 	} else {
 		Buffer newBuffer = std::make_shared<BufferImpl>(buffer->size);
-		memcpy(newBuffer->data, buffer->data, buffer->size);
+		if (buffer->size > 0) {
+			memcpy(newBuffer->data, buffer->data, buffer->size);
+		}
 		return newBuffer;
 	}
 }
 
 Buffer buffer_create(int64_t newSize) {
-	if (newSize <= 0) {
-		throwError(U"buffer_create: Cannot create buffer of size ", newSize, U".\n");
-		return Buffer();
+	if (newSize < 0) newSize = 0;
+	if (newSize == 0) {
+		// Allocate empty head to indicate that an empty buffer exists.
+		return std::make_shared<BufferImpl>();
 	} else {
+		// Allocate head and data.
 		return std::make_shared<BufferImpl>(newSize);
 	}
 }
 
 Buffer buffer_create(int64_t newSize, uint8_t *newData) {
-	if (newSize <= 0) {
-		throwError(U"buffer_create: Cannot create buffer of size ", newSize, U" (with existing data).\n");
-		return Buffer();
-	} else {
-		return std::make_shared<BufferImpl>(newSize, newData);
-	}
+	if (newSize < 0) newSize = 0;
+	return std::make_shared<BufferImpl>(newSize, newData);
 }
 
 void buffer_replaceDestructor(const Buffer &buffer, const std::function<void(uint8_t *)>& newDestructor) {
 	if (!buffer_exists(buffer)) {
 		throwError(U"buffer_replaceDestructor: Cannot replace destructor for a buffer that don't exist.\n");
-	} else {
+	} else if (buffer->bufferSize > 0) {
 		buffer->destructor = newDestructor;
 	}
 }
@@ -156,7 +164,7 @@ uint8_t* buffer_dangerous_getUnsafeData(const Buffer &buffer) {
 void buffer_setBytes(const Buffer &buffer, uint8_t value) {
 	if (!buffer_exists(buffer)) {
 		throwError(U"buffer_setBytes: Cannot set bytes for a buffer that don't exist.\n");
-	} else {
+	} else if (buffer->bufferSize > 0) {
 		memset(buffer->data, value, buffer->bufferSize);
 	}
 }

+ 31 - 9
Source/DFPSR/api/bufferAPI.h

@@ -29,6 +29,23 @@
 #include <functional>
 #include "../base/SafePointer.h"
 
+// The types of buffer handles to consider when designing algorithms:
+// * Null handle suggesting that there is nothing, such as when loading a file failed.
+//     Size does not exist, but is substituted with zero when asked.
+//     buffer_exists(Buffer()) == false
+//     buffer_dangerous_getUnsafeData(Buffer()) == nullptr
+//     buffer_getSize(Buffer()) == 0
+// * Empty head, used when loading a file worked but the file itself contained no data.
+//     Size equals zero, but stored in the head.
+//     buffer_exists(buffer_create(0)) == true
+//     buffer_dangerous_getUnsafeData(buffer_create(0)) == nullptr
+//     buffer_getSize(buffer_create(0)) == 0
+// * Buffer containing data, when the file contained data.
+//     When bytes is greater than zero.
+//     buffer_exists(buffer_create(bytes)) == true
+//     buffer_dangerous_getUnsafeData(buffer_create(x)) == zeroedData
+//     buffer_getSize(buffer_create(bytes)) == bytes
+
 namespace dsr {
 	// A safer replacement for raw memory allocation when you don't need to resize the content.
 	// Guarantees that internal addresses will not be invalidated during its lifetime.
@@ -36,20 +53,24 @@ namespace dsr {
 	class BufferImpl;
 	using Buffer = std::shared_ptr<BufferImpl>;
 
-	// Creates a new buffer of newSize bytes.
-	// Pre-condition: newSize > 0
+	// Side-effect: Creates a new buffer head regardless of newSize, but only allocates a zeroed data allocation if newSize > 0.
+	// Post-condition: Returns a handle to the new buffer.
+	// Creating a buffer without a size will only allocate the buffer's head referring to null data with size zero.
 	Buffer buffer_create(int64_t newSize);
 
-	// Creates a new buffer of newSize bytes inheriting ownership of newData.
+	// Side-effect: Creates a new buffer of newSize bytes inheriting ownership of newData.
 	//   If the given data cannot be freed as a C allocation, replaceDestructor must be called with the special destructor.
-	// Pre-condition: newSize > 0
+	// Pre-condition: newSize may not be larger than the size of newData in bytes.
+	//   Breaking this pre-condition may cause crashes, so only provide a newData pointer if you know what you are doing.
+	// Post-condition: Returns a handle to the manually constructed buffer.
 	Buffer buffer_create(int64_t newSize, uint8_t *newData);
 
 	// Sets the allocation's destructor, to be called when there are no more reference counted pointers to the buffer.
-	// Pre-condition: buffer exists
+	// Pre-condition: The buffer exists.
+	//   If the buffer has a head but no data allocation, the command will be ignored because there is no allocation to delete.
 	void buffer_replaceDestructor(const Buffer &buffer, const std::function<void(uint8_t *)>& newDestructor);
 
-	// Returns true iff buffer exists
+	// Returns true iff buffer exists, even if it is empty without any data allocation.
 	inline bool buffer_exists(Buffer buffer) {
 		return buffer.get() != nullptr;
 	}
@@ -59,20 +80,20 @@ namespace dsr {
 	Buffer buffer_clone(const Buffer &buffer);
 
 	// Returns the buffer's size in bytes, as given when allocating it excluding allocation padding.
-	// Returns zero if buffer doesn't exist.
+	// Returns zero if buffer doesn't exist or has no data allocated.
 	int64_t buffer_getSize(const Buffer &buffer);
 
 	// Returns the number of reference counted handles to the buffer, or 0 if the buffer does not exist.
 	int64_t buffer_getUseCount(const Buffer &buffer);
 
 	// Returns a raw pointer to the data.
-	// An empty handle will return nullptr.
+	// An empty handle or buffer of length zero without data will return nullptr.
 	uint8_t* buffer_dangerous_getUnsafeData(const Buffer &buffer);
 
 	// A wrapper for getting a bound-checked pointer of the correct element type.
 	//   Only cast to trivially packed types with power of two dimensions so that the compiler does not add padding.
 	// The name must be an ansi encoded constant literal, because each String contains a Buffer which would cause a cyclic dependency.
-	// Returns a safe null pointer if buffer does not exist.
+	// Returns a safe null pointer if buffer does not exist or there is no data allocation.
 	template <typename T>
 	SafePointer<T> buffer_getSafeData(const Buffer &buffer, const char* name) {
 		if (!buffer_exists(buffer)) {
@@ -85,6 +106,7 @@ namespace dsr {
 
 	// Set all bytes to the same value.
 	// Pre-condition: buffer exists, or else an exception is thrown to warn you.
+	//   If the buffer has a head but no data allocation, the command will be ignored because there are no bytes to set.
 	void buffer_setBytes(const Buffer &buffer, uint8_t value);
 }