sm64coopdx/data/dynos_mgr_models.cpp
Isaac0-dev 92fbe54ebd separate dynos pack model and mod owned model ids
to address the grand star late join model bug that can happen with mods like omm.
the bug happened when players have different dynos packs, and during a late join, the model ids are relied on to be the same.
dynos packs can offset the available model ids, therefore desyncing the model used on late join.
2024-11-28 14:02:27 +10:00

258 lines
7.2 KiB
C++

#include <map>
#include <vector>
#include "dynos.cpp.h"
extern "C" {
#include "engine/geo_layout.h"
#include "engine/graph_node.h"
#include "model_ids.h"
}
#define VANILLA_ID_END 255
enum ModelLoadType {
MLT_GEO,
MLT_DL,
MLT_STORE,
};
struct ModelInfo {
u32 id;
void* asset;
struct GraphNode* graphNode;
enum ModelPool modelPool;
};
static struct DynamicPool* sModelPools[MODEL_POOL_MAX] = { 0 };
static std::map<void*, struct ModelInfo> sAssetMap[MODEL_POOL_MAX];
static std::map<u32, std::vector<struct ModelInfo>> sIdMap;
static std::map<u32, u32> sOverwriteMap;
static u32 find_empty_id(bool aIsPermanent) {
u32 id = aIsPermanent ? 9999 : VANILLA_ID_END + 1;
s8 dir = aIsPermanent ? -1 : 1;
while (true) {
if (id != 9999) {
if (sIdMap.count(id) == 0) { return id; }
if (sIdMap[id].size() == 0) { return id; }
}
id += dir;
}
}
void DynOS_Model_Dump() {
for (auto& it : sIdMap) {
if (it.second.size() == 0 || it.second.empty()) { continue; }
printf(">> [%03x] ", it.first);
for (auto& it2 : it.second) {
switch (it2.modelPool) {
case MODEL_POOL_PERMANENT: printf("P "); break;
case MODEL_POOL_SESSION: printf("S "); break;
case MODEL_POOL_LEVEL: printf("L "); break;
case MODEL_POOL_MAX: printf("M "); break;
}
printf("%p ", it2.graphNode);
}
printf("\n");
}
}
struct GraphNode* DynOS_Model_LoadCommon(u32* aId, enum ModelPool aModelPool, void* aAsset, u8 aLayer, struct GraphNode* aGraphNode, bool aDeDuplicate, enum ModelLoadType mlt) {
// sanity check pool
if (aModelPool >= MODEL_POOL_MAX) { return NULL; }
// allocate pool
if (!sModelPools[aModelPool]) {
sModelPools[aModelPool] = dynamic_pool_init();
}
// check perm map
auto& permMap = sAssetMap[MODEL_POOL_PERMANENT];
if (aDeDuplicate && permMap.count(aAsset)) {
auto& found = permMap[aAsset];
if (*aId && *aId == found.id) {
return found.graphNode;
}
if (*aId == 0) {
*aId = found.id;
return found.graphNode;
}
}
// check map
auto& map = sAssetMap[aModelPool];
if (aDeDuplicate && map.count(aAsset)) {
auto& found = map[aAsset];
if (*aId && *aId != found.id) {
sOverwriteMap[*aId] = found.id;
}
*aId = found.id;
return found.graphNode;
}
// load geo
struct GraphNode* node = NULL;
switch (mlt) {
case MLT_GEO:
node = process_geo_layout(sModelPools[aModelPool], aAsset);
break;
case MLT_DL:
node = (struct GraphNode *) init_graph_node_display_list(sModelPools[aModelPool], NULL, aLayer, aAsset);
break;
case MLT_STORE:
node = aGraphNode;
break;
}
if (!node) { return NULL; }
// figure out id
if (!*aId) { *aId = find_empty_id(aModelPool == MODEL_POOL_PERMANENT); }
// create model info
struct ModelInfo info = {
.id = *aId,
.asset = aAsset,
.graphNode = node,
.modelPool = aModelPool
};
// store in maps
sIdMap[*aId].push_back(info);
map[aAsset] = info;
return node;
}
struct GraphNode* DynOS_Model_LoadGeo(u32* aId, enum ModelPool aModelPool, void* aAsset, bool aDeDuplicate) {
return DynOS_Model_LoadCommon(aId, aModelPool, aAsset, 0, NULL, aDeDuplicate, MLT_GEO);
}
struct GraphNode* DynOS_Model_LoadDl(u32* aId, enum ModelPool aModelPool, u8 aLayer, void* aAsset) {
return DynOS_Model_LoadCommon(aId, aModelPool, aAsset, aLayer, NULL, true, MLT_DL);
}
struct GraphNode* DynOS_Model_StoreGeo(u32* aId, enum ModelPool aModelPool, void* aAsset, struct GraphNode* aGraphNode) {
return DynOS_Model_LoadCommon(aId, aModelPool, aAsset, 0, aGraphNode, true, MLT_STORE);
}
struct GraphNode* DynOS_Model_GetErrorGeo() {
if (!sIdMap.count(MODEL_ERROR_MODEL)) { return NULL; }
auto& vec = sIdMap[MODEL_ERROR_MODEL];
if (vec.size() == 0 || vec.empty()) {
return NULL;
}
return vec.back().graphNode;
}
struct GraphNode* DynOS_Model_GetGeo(u32 aId) {
if (!aId) { return NULL; }
if (sOverwriteMap.count(aId)) {
aId = sOverwriteMap[aId];
}
if (sIdMap.count(aId) == 0) {
return DynOS_Model_GetErrorGeo();
}
auto& vec = sIdMap[aId];
if (vec.size() == 0 || vec.empty()) {
return DynOS_Model_GetErrorGeo();
}
return vec.back().graphNode;
}
static u32 DynOS_Model_GetIdFromGeoRef(u32 aIndex, void* aGeoRef) {
u32 lowest = 9999;
for (auto& it : sIdMap) {
u32 id = it.first;
if (id > lowest) { continue; }
if (!it.second.size() || it.second.empty()) { continue; }
auto& node = it.second.back();
if (aGeoRef == node.graphNode->georef) {
lowest = id;
}
}
if (lowest < 9999) { return lowest; }
return aIndex;
}
u32 DynOS_Model_GetIdFromGraphNode(struct GraphNode* aNode) {
u32 lowest = 9999;
void* georef = NULL;
for (auto& it : sIdMap) {
u32 id = it.first;
if (id > lowest) { continue; }
if (!it.second.size() || it.second.empty()) { continue; }
auto& node = it.second.back();
if (aNode == node.graphNode) {
lowest = id;
georef = (void*)node.graphNode->georef;
}
}
if (georef) {
lowest = DynOS_Model_GetIdFromGeoRef(lowest, georef);
}
if (lowest < 9999) { return lowest; }
return MODEL_ERROR_MODEL;
}
u32 DynOS_Model_GetIdFromAsset(void* asset) {
if (!asset) { return MODEL_NONE; }
u32 lowest = 9999;
for (int i = 0; i < MODEL_POOL_MAX; i++) {
if (!sAssetMap[i].count(asset)) { continue; }
u32 id = sAssetMap[i][asset].id;
if (id < lowest) { lowest = id; }
if (sOverwriteMap.count(id)) {
id = sOverwriteMap[id];
if (id < lowest) { lowest = id; }
}
}
if (lowest < 9999) { return lowest; }
return MODEL_ERROR_MODEL;
}
void DynOS_Model_OverwriteSlot(u32 srcSlot, u32 dstSlot) {
sOverwriteMap[srcSlot] = dstSlot;
}
void DynOS_Model_ClearPool(enum ModelPool aModelPool) {
if (!sModelPools[aModelPool]) { return; }
// schedule pool to be freed
dynamic_pool_free_pool(sModelPools[aModelPool]);
// clear overwrite
if (aModelPool == MODEL_POOL_LEVEL) {
sOverwriteMap.clear();
}
// clear maps
auto& assetMap = sAssetMap[aModelPool];
for (auto& asset : assetMap) {
auto& info = asset.second;
if (sIdMap.count(info.id) == 0) { continue; }
// preventing clearing permanent vanilla model slot
if (info.id <= VANILLA_ID_END && sIdMap.count(info.id) <= 1) {
if (sAssetMap[MODEL_POOL_PERMANENT].count(info.asset) > 0) {
continue;
}
}
// erase from id map
auto& idMap = sIdMap[info.id];
for (auto info2 = idMap.begin(); info2 != idMap.end(); ) {
if (info.id == info2->id && info2->modelPool == aModelPool) {
info2 = idMap.erase(info2);
} else {
info2++;
}
}
}
assetMap.clear();
}