@@ -85,14 +85,6 @@ class CowData {
8585
8686 // internal helpers
8787
88- static _FORCE_INLINE_ SafeNumeric<USize> *_get_refcount_ptr (uint8_t *p_ptr) {
89- return (SafeNumeric<USize> *)(p_ptr + REF_COUNT_OFFSET);
90- }
91-
92- static _FORCE_INLINE_ USize *_get_size_ptr (uint8_t *p_ptr) {
93- return (USize *)(p_ptr + SIZE_OFFSET);
94- }
95-
9688 static _FORCE_INLINE_ T *_get_data_ptr (uint8_t *p_ptr) {
9789 return (T *)(p_ptr + DATA_OFFSET);
9890 }
@@ -146,8 +138,23 @@ class CowData {
146138 void _unref ();
147139 void _ref (const CowData *p_from);
148140 void _ref (const CowData &p_from);
149- USize _copy_on_write ();
150- Error _realloc (Size p_alloc_size);
141+
142+ // Ensures that the backing buffer is at least p_size wide, and that this CowData instance is
143+ // the only reference to it. The buffer is populated with as many element copies from the old
144+ // array as possible.
145+ // It is the responsibility of the caller to populate newly allocated space up to p_size.
146+ Error _fork_allocate (USize p_size);
147+ Error _copy_on_write () { return _fork_allocate (size ()); }
148+
149+ // Allocates a backing array of the given capacity. The reference count is initialized to 1.
150+ // It is the responsibility of the caller to populate the array and the new size property.
151+ Error _alloc (USize p_alloc_size);
152+
153+ // Re-allocates the backing array to the given capacity. The reference count is initialized to 1.
154+ // It is the responsibility of the caller to populate the array and the new size property.
155+ // The caller must also make sure there are no other references to the data, as pointers may
156+ // be invalidated.
157+ Error _realloc (USize p_alloc_size);
151158
152159public:
153160 void operator =(const CowData<T> &p_from) { _ref (p_from); }
@@ -179,7 +186,7 @@ class CowData {
179186 }
180187 }
181188
182- _FORCE_INLINE_ void clear () { resize ( 0 ); }
189+ _FORCE_INLINE_ void clear () { _unref ( ); }
183190 _FORCE_INLINE_ bool is_empty () const { return _ptr == nullptr ; }
184191
185192 _FORCE_INLINE_ void set (Size p_index, const T &p_elem) {
@@ -253,7 +260,8 @@ void CowData<T>::_unref() {
253260 _ptr = nullptr ;
254261 return ;
255262 }
256- // Clean up.
263+ // We had the only reference; destroy the data.
264+
257265 // First, invalidate our own reference.
258266 // NOTE: It is required to do so immediately because it must not be observable outside of this
259267 // function after refcount has already been reduced to 0.
@@ -271,136 +279,136 @@ void CowData<T>::_unref() {
271279 }
272280 }
273281
274- // free mem
282+ // Free memory.
275283 Memory::free_static ((uint8_t *)prev_ptr - DATA_OFFSET, false );
276284}
277285
278286template <typename T>
279- typename CowData<T>::USize CowData<T>::_copy_on_write() {
280- if (!_ptr) {
281- return 0 ;
287+ Error CowData<T>::_fork_allocate(USize p_size) {
288+ if (p_size == 0 ) {
289+ // Wants to clean up.
290+ _unref ();
291+ return OK;
282292 }
283293
284- SafeNumeric<USize> *refc = _get_refcount ();
285-
286- USize rc = refc->get ();
287- if (unlikely (rc > 1 )) {
288- /* in use by more than me */
289- USize current_size = *_get_size ();
294+ USize alloc_size;
295+ ERR_FAIL_COND_V (!_get_alloc_size_checked (p_size, &alloc_size), ERR_OUT_OF_MEMORY);
290296
291- uint8_t *mem_new = (uint8_t *)Memory::alloc_static (_get_alloc_size (current_size) + DATA_OFFSET, false );
292- ERR_FAIL_NULL_V (mem_new, 0 );
297+ const USize prev_size = size ();
293298
294- SafeNumeric<USize> *_refc_ptr = _get_refcount_ptr (mem_new);
295- USize *_size_ptr = _get_size_ptr (mem_new);
296- T *_data_ptr = _get_data_ptr (mem_new);
299+ if (!_ptr) {
300+ // We had no data before; just allocate a new array.
301+ const Error error = _alloc (alloc_size);
302+ if (error) {
303+ return error;
304+ }
305+ } else if (_get_refcount ()->get () == 1 ) {
306+ // Resize in-place.
307+ // NOTE: This case is not just an optimization, but required, as some callers depend on
308+ // `_copy_on_write()` calls not changing the pointer after the first fork
309+ // (e.g. mutable iterators).
310+ if (p_size == prev_size) {
311+ // We can shortcut here; we don't need to do anything.
312+ return OK;
313+ }
297314
298- new (_refc_ptr) SafeNumeric<USize>(1 ); // refcount
299- *(_size_ptr) = current_size; // size
315+ // Destroy extraneous elements.
316+ if constexpr (!std::is_trivially_destructible_v<T>) {
317+ for (USize i = prev_size; i > p_size; i--) {
318+ _ptr[i - 1 ].~T ();
319+ }
320+ }
300321
301- // initialize new elements
302- if constexpr (std::is_trivially_copyable_v<T>) {
303- memcpy ((uint8_t *)_data_ptr, _ptr, current_size * sizeof (T));
304- } else {
305- for (USize i = 0 ; i < current_size; i++) {
306- memnew_placement (&_data_ptr[i], T (_ptr[i]));
322+ if (alloc_size != _get_alloc_size (prev_size)) {
323+ const Error error = _realloc (alloc_size);
324+ if (error) {
325+ // Out of memory; the current array is still valid though.
326+ return error;
307327 }
308328 }
329+ } else {
330+ // Resize by forking.
331+
332+ // Create a temporary CowData to hold ownership over our _ptr.
333+ // It will be used to copy elements from the old buffer over to our new buffer.
334+ // At the end of the block, it will be automatically destructed by going out of scope.
335+ const CowData prev_data;
336+ prev_data._ptr = _ptr;
337+ _ptr = nullptr ;
309338
310- _unref ();
311- _ptr = _data_ptr;
339+ const Error error = _alloc (alloc_size);
340+ if (error) {
341+ // On failure to allocate, just give up the old data and return.
342+ // We could recover our old pointer from prev_data, but by just dropping our data, we
343+ // consciously invite early failure for the case that the caller does not handle this
344+ // case gracefully.
345+ return error;
346+ }
312347
313- rc = 1 ;
348+ // Copy over elements.
349+ const USize copied_element_count = MIN (prev_size, p_size);
350+ if (copied_element_count > 0 ) {
351+ if constexpr (std::is_trivially_copyable_v<T>) {
352+ memcpy ((uint8_t *)_ptr, (uint8_t *)prev_data._ptr , copied_element_count * sizeof (T));
353+ } else {
354+ for (USize i = 0 ; i < copied_element_count; i++) {
355+ memnew_placement (&_ptr[i], T (prev_data._ptr [i]));
356+ }
357+ }
358+ }
314359 }
315- return rc;
360+
361+ // Set our new size.
362+ *_get_size () = p_size;
363+
364+ return OK;
316365}
317366
318367template <typename T>
319368template <bool p_ensure_zero>
320369Error CowData<T>::resize(Size p_size) {
321370 ERR_FAIL_COND_V (p_size < 0 , ERR_INVALID_PARAMETER);
322371
323- Size current_size = size ();
324-
325- if (p_size == current_size) {
372+ const Size prev_size = size ();
373+ if (p_size == prev_size) {
326374 return OK;
327375 }
328376
329- if (p_size == 0 ) {
330- // Wants to clean up.
331- _unref (); // Resets _ptr to nullptr.
332- return OK;
377+ const Error error = _fork_allocate (p_size);
378+ if (error) {
379+ return error;
333380 }
334381
335- // possibly changing size, copy on write
336- _copy_on_write ();
337-
338- USize current_alloc_size = _get_alloc_size (current_size);
339- USize alloc_size;
340- ERR_FAIL_COND_V (!_get_alloc_size_checked (p_size, &alloc_size), ERR_OUT_OF_MEMORY);
341-
342- if (p_size > current_size) {
343- if (alloc_size != current_alloc_size) {
344- if (current_size == 0 ) {
345- // alloc from scratch
346- uint8_t *mem_new = (uint8_t *)Memory::alloc_static (alloc_size + DATA_OFFSET, false );
347- ERR_FAIL_NULL_V (mem_new, ERR_OUT_OF_MEMORY);
348-
349- SafeNumeric<USize> *_refc_ptr = _get_refcount_ptr (mem_new);
350- USize *_size_ptr = _get_size_ptr (mem_new);
351- T *_data_ptr = _get_data_ptr (mem_new);
352-
353- new (_refc_ptr) SafeNumeric<USize>(1 ); // refcount
354- *(_size_ptr) = 0 ; // size, currently none
355-
356- _ptr = _data_ptr;
357-
358- } else {
359- const Error error = _realloc (alloc_size);
360- if (error) {
361- return error;
362- }
363- }
364- }
365-
366- // construct the newly created elements
367- memnew_arr_placement<p_ensure_zero>(_ptr + current_size, p_size - current_size);
382+ if (p_size > prev_size) {
383+ memnew_arr_placement<p_ensure_zero>(_ptr + prev_size, p_size - prev_size);
384+ }
368385
369- *_get_size () = p_size;
386+ return OK;
387+ }
370388
371- } else if (p_size < current_size) {
372- if constexpr (!std::is_trivially_destructible_v<T>) {
373- // deinitialize no longer needed elements
374- for (USize i = p_size; i < *_get_size (); i++) {
375- T *t = &_ptr[i];
376- t->~T ();
377- }
378- }
389+ template <typename T>
390+ Error CowData<T>::_alloc(USize p_alloc_size) {
391+ uint8_t *mem_new = (uint8_t *)Memory::alloc_static (p_alloc_size + DATA_OFFSET, false );
392+ ERR_FAIL_NULL_V (mem_new, ERR_OUT_OF_MEMORY);
379393
380- if (alloc_size != current_alloc_size) {
381- const Error error = _realloc (alloc_size);
382- if (error) {
383- return error;
384- }
385- }
394+ _ptr = _get_data_ptr (mem_new);
386395
387- * _get_size () = p_size;
388- }
396+ // If we alloc, we're guaranteed to be the only reference.
397+ new ( _get_refcount ()) SafeNumeric<USize>( 1 );
389398
390399 return OK;
391400}
392401
393402template <typename T>
394- Error CowData<T>::_realloc(Size p_alloc_size) {
403+ Error CowData<T>::_realloc(USize p_alloc_size) {
395404 uint8_t *mem_new = (uint8_t *)Memory::realloc_static (((uint8_t *)_ptr) - DATA_OFFSET, p_alloc_size + DATA_OFFSET, false );
396405 ERR_FAIL_NULL_V (mem_new, ERR_OUT_OF_MEMORY);
397406
398- SafeNumeric<USize> *_refc_ptr = _get_refcount_ptr (mem_new);
399- T *_data_ptr = _get_data_ptr (mem_new);
407+ _ptr = _get_data_ptr (mem_new);
400408
401409 // If we realloc, we're guaranteed to be the only reference.
402- new (_refc_ptr) SafeNumeric<USize>( 1 );
403- _ptr = _data_ptr ;
410+ // So the reference was 1 and was copied to be 1 again.
411+ DEV_ASSERT ( _get_refcount ()-> get () == 1 ) ;
404412
405413 return OK;
406414}
0 commit comments