When a `GPencilUpdateCacheNode` is created, it always allocates the `children` pointer. This should not be freed until the whole cache is deleted. The `cache_node_update` would free the `children` pointer in a specific case, causing a double-free later when the cache was removed.
259 lines
7.9 KiB
C
259 lines
7.9 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later
|
|
* Copyright 2022 Blender Foundation. */
|
|
|
|
/** \file
|
|
* \ingroup bke
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
|
|
#include "BKE_gpencil_update_cache.h"
|
|
|
|
#include "BLI_dlrbTree.h"
|
|
#include "BLI_listbase.h"
|
|
|
|
#include "BKE_gpencil.h"
|
|
|
|
#include "DNA_gpencil_types.h"
|
|
#include "DNA_userdef_types.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
static GPencilUpdateCache *update_cache_alloc(int index, int flag, void *data)
|
|
{
|
|
GPencilUpdateCache *new_cache = MEM_callocN(sizeof(GPencilUpdateCache), __func__);
|
|
new_cache->children = BLI_dlrbTree_new();
|
|
new_cache->flag = flag;
|
|
new_cache->index = index;
|
|
new_cache->data = data;
|
|
|
|
return new_cache;
|
|
}
|
|
|
|
static short cache_node_compare(void *node, void *data)
|
|
{
|
|
int index_a = ((GPencilUpdateCacheNode *)node)->cache->index;
|
|
int index_b = ((GPencilUpdateCache *)data)->index;
|
|
if (index_a == index_b) {
|
|
return 0;
|
|
}
|
|
return index_a < index_b ? 1 : -1;
|
|
}
|
|
|
|
static DLRBT_Node *cache_node_alloc(void *data)
|
|
{
|
|
GPencilUpdateCacheNode *new_node = MEM_callocN(sizeof(GPencilUpdateCacheNode), __func__);
|
|
new_node->cache = ((GPencilUpdateCache *)data);
|
|
return (DLRBT_Node *)new_node;
|
|
}
|
|
|
|
static void cache_node_free(void *node);
|
|
|
|
static void update_cache_free(GPencilUpdateCache *cache)
|
|
{
|
|
BLI_dlrbTree_free(cache->children, cache_node_free);
|
|
MEM_SAFE_FREE(cache->children);
|
|
MEM_freeN(cache);
|
|
}
|
|
|
|
static void cache_node_free(void *node)
|
|
{
|
|
GPencilUpdateCache *cache = ((GPencilUpdateCacheNode *)node)->cache;
|
|
if (cache != NULL) {
|
|
update_cache_free(cache);
|
|
}
|
|
MEM_freeN(node);
|
|
}
|
|
|
|
static void cache_node_update(void *node, void *data)
|
|
{
|
|
GPencilUpdateCache *update_cache = ((GPencilUpdateCacheNode *)node)->cache;
|
|
GPencilUpdateCache *new_update_cache = (GPencilUpdateCache *)data;
|
|
|
|
/* If the new cache is already "covered" by the current cache, just free it and return. */
|
|
if (new_update_cache->flag <= update_cache->flag) {
|
|
update_cache_free(new_update_cache);
|
|
return;
|
|
}
|
|
|
|
update_cache->data = new_update_cache->data;
|
|
update_cache->flag = new_update_cache->flag;
|
|
|
|
/* In case the new cache does a full update, remove its children since they will be all
|
|
* updated by this cache. */
|
|
if (new_update_cache->flag == GP_UPDATE_NODE_FULL_COPY) {
|
|
BLI_dlrbTree_free(update_cache->children, cache_node_free);
|
|
}
|
|
|
|
update_cache_free(new_update_cache);
|
|
}
|
|
|
|
static void update_cache_node_create_ex(GPencilUpdateCache *root_cache,
|
|
void *data,
|
|
int gpl_index,
|
|
int gpf_index,
|
|
int gps_index,
|
|
bool full_copy)
|
|
{
|
|
if (root_cache->flag == GP_UPDATE_NODE_FULL_COPY) {
|
|
/* Entire data-block has to be recalculated, e.g. nothing else needs to be added to the cache.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
const int node_flag = full_copy ? GP_UPDATE_NODE_FULL_COPY : GP_UPDATE_NODE_LIGHT_COPY;
|
|
|
|
if (gpl_index == -1) {
|
|
root_cache->data = (bGPdata *)data;
|
|
root_cache->flag = node_flag;
|
|
if (full_copy) {
|
|
/* Entire data-block has to be recalculated, remove all caches of "lower" elements. */
|
|
BLI_dlrbTree_free(root_cache->children, cache_node_free);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const bool is_layer_update_node = (gpf_index == -1);
|
|
/* If the data pointer in #GPencilUpdateCache is NULL, this element is not actually cached
|
|
* and does not need to be updated, but we do need the index to find elements that are in
|
|
* levels below. E.g. if a stroke needs to be updated, the frame it is in would not hold a
|
|
* pointer to it's data. */
|
|
GPencilUpdateCache *gpl_cache = update_cache_alloc(
|
|
gpl_index,
|
|
is_layer_update_node ? node_flag : GP_UPDATE_NODE_NO_COPY,
|
|
is_layer_update_node ? (bGPDlayer *)data : NULL);
|
|
GPencilUpdateCacheNode *gpl_node = (GPencilUpdateCacheNode *)BLI_dlrbTree_add(
|
|
root_cache->children, cache_node_compare, cache_node_alloc, cache_node_update, gpl_cache);
|
|
|
|
BLI_dlrbTree_linkedlist_sync(root_cache->children);
|
|
if (gpl_node->cache->flag == GP_UPDATE_NODE_FULL_COPY || is_layer_update_node) {
|
|
return;
|
|
}
|
|
|
|
const bool is_frame_update_node = (gps_index == -1);
|
|
GPencilUpdateCache *gpf_cache = update_cache_alloc(
|
|
gpf_index,
|
|
is_frame_update_node ? node_flag : GP_UPDATE_NODE_NO_COPY,
|
|
is_frame_update_node ? (bGPDframe *)data : NULL);
|
|
GPencilUpdateCacheNode *gpf_node = (GPencilUpdateCacheNode *)BLI_dlrbTree_add(
|
|
gpl_node->cache->children,
|
|
cache_node_compare,
|
|
cache_node_alloc,
|
|
cache_node_update,
|
|
gpf_cache);
|
|
|
|
BLI_dlrbTree_linkedlist_sync(gpl_node->cache->children);
|
|
if (gpf_node->cache->flag == GP_UPDATE_NODE_FULL_COPY || is_frame_update_node) {
|
|
return;
|
|
}
|
|
|
|
GPencilUpdateCache *gps_cache = update_cache_alloc(gps_index, node_flag, (bGPDstroke *)data);
|
|
BLI_dlrbTree_add(gpf_node->cache->children,
|
|
cache_node_compare,
|
|
cache_node_alloc,
|
|
cache_node_update,
|
|
gps_cache);
|
|
|
|
BLI_dlrbTree_linkedlist_sync(gpf_node->cache->children);
|
|
}
|
|
|
|
static void update_cache_node_create(
|
|
bGPdata *gpd, bGPDlayer *gpl, bGPDframe *gpf, bGPDstroke *gps, bool full_copy)
|
|
{
|
|
if (gpd == NULL) {
|
|
return;
|
|
}
|
|
|
|
GPencilUpdateCache *root_cache = gpd->runtime.update_cache;
|
|
if (root_cache == NULL) {
|
|
gpd->runtime.update_cache = update_cache_alloc(0, GP_UPDATE_NODE_NO_COPY, NULL);
|
|
root_cache = gpd->runtime.update_cache;
|
|
}
|
|
|
|
if (root_cache->flag == GP_UPDATE_NODE_FULL_COPY) {
|
|
/* Entire data-block has to be recalculated, e.g. nothing else needs to be added to the cache.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
const int gpl_index = (gpl != NULL) ? BLI_findindex(&gpd->layers, gpl) : -1;
|
|
const int gpf_index = (gpl != NULL && gpf != NULL) ? BLI_findindex(&gpl->frames, gpf) : -1;
|
|
const int gps_index = (gpf != NULL && gps != NULL) ? BLI_findindex(&gpf->strokes, gps) : -1;
|
|
|
|
void *data = gps;
|
|
if (!data) {
|
|
data = gpf;
|
|
}
|
|
if (!data) {
|
|
data = gpl;
|
|
}
|
|
if (!data) {
|
|
data = gpd;
|
|
}
|
|
|
|
update_cache_node_create_ex(root_cache, data, gpl_index, gpf_index, gps_index, full_copy);
|
|
}
|
|
|
|
static void gpencil_traverse_update_cache_ex(GPencilUpdateCache *parent_cache,
|
|
GPencilUpdateCacheTraverseSettings *ts,
|
|
int depth,
|
|
void *user_data)
|
|
{
|
|
if (BLI_listbase_is_empty((ListBase *)parent_cache->children)) {
|
|
return;
|
|
}
|
|
|
|
LISTBASE_FOREACH (GPencilUpdateCacheNode *, cache_node, parent_cache->children) {
|
|
GPencilUpdateCache *cache = cache_node->cache;
|
|
|
|
GPencilUpdateCacheIter_Cb cb = ts->update_cache_cb[depth];
|
|
if (cb != NULL) {
|
|
bool skip = cb(cache, user_data);
|
|
if (skip) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
gpencil_traverse_update_cache_ex(cache, ts, depth + 1, user_data);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Update Cache API
|
|
*
|
|
* \{ */
|
|
|
|
GPencilUpdateCache *BKE_gpencil_create_update_cache(void *data, bool full_copy)
|
|
{
|
|
return update_cache_alloc(
|
|
0, full_copy ? GP_UPDATE_NODE_FULL_COPY : GP_UPDATE_NODE_LIGHT_COPY, data);
|
|
}
|
|
|
|
void BKE_gpencil_traverse_update_cache(GPencilUpdateCache *cache,
|
|
GPencilUpdateCacheTraverseSettings *ts,
|
|
void *user_data)
|
|
{
|
|
gpencil_traverse_update_cache_ex(cache, ts, 0, user_data);
|
|
}
|
|
|
|
void BKE_gpencil_tag_full_update(bGPdata *gpd, bGPDlayer *gpl, bGPDframe *gpf, bGPDstroke *gps)
|
|
{
|
|
update_cache_node_create(gpd, gpl, gpf, gps, true);
|
|
}
|
|
|
|
void BKE_gpencil_tag_light_update(bGPdata *gpd, bGPDlayer *gpl, bGPDframe *gpf, bGPDstroke *gps)
|
|
{
|
|
update_cache_node_create(gpd, gpl, gpf, gps, false);
|
|
}
|
|
|
|
void BKE_gpencil_free_update_cache(bGPdata *gpd)
|
|
{
|
|
GPencilUpdateCache *gpd_cache = gpd->runtime.update_cache;
|
|
if (gpd_cache) {
|
|
update_cache_free(gpd_cache);
|
|
gpd->runtime.update_cache = NULL;
|
|
}
|
|
}
|
|
|
|
/** \} */
|