Make LoadInteriorRefList async without crash using SKSE Task interface

This commit is contained in:
Tyler Hallada 2020-10-16 17:43:02 -04:00
parent 5afc919f03
commit b3dd9e240d

View File

@ -36,12 +36,12 @@ int CreateInteriorRefListImpl(RE::BSFixedString api_url, RE::BSFixedString api_k
const RE::TESBoundObject * base = ref->GetBaseObject();
if (base) {
RE::FormID base_form_id = base->GetFormID();
if (base_form_id == 7) {
// skip saving player ref
const RE::FormType form_type = base->GetFormType();
if (base_form_id == 0x7 || form_type == RE::FormType::ActorCharacter) {
// skip saving player ref or other companions
continue;
}
const char * form_name = base->GetName();
const RE::FormType form_type = base->GetFormType();
logger::info(FMT_STRING("CreateInteriorRefList form: {} ({:x})"), form_name, (uint32_t)form_type);
float position_x = ref->GetPositionX();
float position_y = ref->GetPositionY();
@ -131,7 +131,7 @@ bool ClearCell(RE::StaticFunctionTag*)
}
// Destroy existing references
for (auto entry = cell->references.begin(); entry != cell->references.end(); ++entry)
for (auto entry = cell->references.begin(); entry != cell->references.end();)
{
RE::TESObjectREFR * ref = (*entry).get();
RE::FormID form_id = ref->GetFormID();
@ -152,11 +152,13 @@ bool ClearCell(RE::StaticFunctionTag*)
} else {
logger::info(FMT_STRING("ClearCell ref not in ANY file! {:x} {}"), (uint32_t)form_id, ref->GetName());
}
++entry;
}
else {
logger::info(FMT_STRING("ClearCell ref is a temp ref, deleting {:x} {}"), (uint32_t)form_id, ref->GetName());
ref->Disable(); // disabling first is required to prevent CTD on unloading cell
ref->SetDelete(true);
cell->references.erase(*entry++); // prevents slowdowns after many runs of ClearCell
}
}
@ -210,9 +212,6 @@ bool LoadInteriorRefListImpl(RE::BSFixedString api_url, RE::BSFixedString api_ke
return false;
}
SKSE::RegistrationMap<bool> regMap = SKSE::RegistrationMap<bool>();
regMap.Register(quest, RE::BSFixedString("OnLoadInteriorRefList"));
RE::TESDataHandler * data_handler = RE::TESDataHandler::GetSingleton();
RE::BSScript::Internal::VirtualMachine * a_vm = RE::BSScript::Internal::VirtualMachine::GetSingleton();
using func_t = decltype(&PlaceAtMe);
@ -234,88 +233,92 @@ bool LoadInteriorRefListImpl(RE::BSFixedString api_url, RE::BSFixedString api_ke
logger::info(FMT_STRING("LoadInteriorRefList lookup cell override name: {} id: {:x}"), cell->GetName(), (uint32_t)cell->GetFormID());
if (!cell) {
logger::error("LoadInteriorRefList cell is null!");
regMap.SendEvent(false);
regMap.Unregister(quest);
return false;
}
//RE::TESObjectREFR * x_marker = data_handler->LookupForm<RE::TESObjectREFR>(6628, "BazaarRealm.esp");
if (target_ref) {
FFIResult<RefRecordVec> result = get_interior_ref_list(api_url.c_str(), api_key.c_str(), interior_ref_list_id);
if (result.IsOk()) {
logger::info("LoadInteriorRefList get_interior_ref_list result: OK");
RefRecordVec vec = result.AsOk();
logger::info(FMT_STRING("LoadInteriorRefList vec len: {:d}, cap: {:d}"), vec.len, vec.cap);
for (int i = 0; i < vec.len; i++) {
RefRecord ref = vec.ptr[i];
logger::info(FMT_STRING("LoadInteriorRefList ref base_mod_name: {}, base_local_form_id: {:x}"), ref.base_mod_name, ref.base_local_form_id);
logger::info(FMT_STRING("LoadInteriorRefList ref position {:.2f} {:.2f} {:.2f}, angle: {:.2f} {:.2f} {:.2f}, scale: {:d}"), ref.position_x, ref.position_y, ref.position_z, ref.angle_x, ref.angle_y, ref.angle_z, ref.scale);
if (ref.base_local_form_id == 7) {
logger::info("LoadInteriorRefList skipping player ref");
continue;
}
// Placing the refs must be done on the main thread otherwise calling MoveTo causes a crash
auto task = SKSE::GetTaskInterface();
task->AddTask([result, target_ref, quest, cell, data_handler, a_vm, MoveTo_Native, PlaceAtMe_Native]() {
SKSE::RegistrationMap<bool> regMap = SKSE::RegistrationMap<bool>();
regMap.Register(quest, RE::BSFixedString("OnLoadInteriorRefList"));
RE::NiPoint3 position = RE::NiPoint3::NiPoint3(ref.position_x, ref.position_y, ref.position_z);
RE::NiPoint3 angle = RE::NiPoint3::NiPoint3(ref.angle_x, ref.angle_y, ref.angle_z);
RE::TESObjectREFR * game_ref = nullptr;
if (ref.ref_mod_name) { // non-temp ref, try to lookup and reposition
game_ref = data_handler->LookupForm<RE::TESObjectREFR>(ref.ref_local_form_id, ref.ref_mod_name);
if (game_ref) {
logger::info(FMT_STRING("LoadInteriorRefList lookup ref name: {}, form_id: {:x}"), game_ref->GetName(), (uint32_t)game_ref->GetFormID());
if (result.IsOk()) {
logger::info("LoadInteriorRefList get_interior_ref_list result: OK");
RefRecordVec vec = result.AsOk();
logger::info(FMT_STRING("LoadInteriorRefList vec len: {:d}, cap: {:d}"), vec.len, vec.cap);
// Failed experiment at trying to call the ObjectReference.Enable() papyrus native function
//RE::BSTSmartPointer<RE::BSScript::Object> object;
//RE::BSTSmartPointer<RE::BSScript::IStackCallbackFunctor> callback = RE::BSTSmartPointer<RE::BSScript::IStackCallbackFunctor>::BSTSmartPointer(RE::BSScript::IStackCallbackFunctor());
//a_vm->CreateObject(RE::BSFixedString("ObjectReference"), game_ref, object);
//_MESSAGE("Created Object isConstructed: %d, isInitialized: %d, isValid: %d, type: %s", object->IsConstructed(), object->IsInitialized(), object->IsValid(), object->GetTypeInfo()->GetName());
//RE::BSTHashMap<RE::VMStackID, RE::BSTSmartPointer<RE::BSScript::Stack>> runningStacks = a_vm->allRunningStacks;
//_MESSAGE("allRunningStacks size: %d", runningStacks.size());
//for (auto entry = runningStacks.begin(); entry != runningStacks.end(); ++entry) {
//_MESSAGE("allRunningStacks %d", entry->first);
//}
//a_vm->DispatchMethodCall(object, RE::BSFixedString("Enable"), RE::MakeFunctionArguments<>(), RE::BSScript::IStackCallbackFunctor::IStackCallbackFunctor())
}
else {
logger::info(FMT_STRING("LoadInteriorRefList lookup ref not found, ref_mod_name: {}, ref_local_form_id: {:x}"), ref.ref_mod_name, ref.ref_local_form_id);
}
}
if (!game_ref) { // temporary reference or non-temp ref not found, recreate from base form
RE::TESForm * form = data_handler->LookupForm(ref.base_local_form_id, ref.base_mod_name);
if (!form) { // form is not found, might be in an uninstalled mod
logger::warn(FMT_STRING("LoadInteriorRefList not spawning ref for form that could not be found in installed mods, base_mod_name: {}, base_local_form_id: {:d}"), ref.base_mod_name, ref.base_local_form_id);
for (int i = 0; i < vec.len; i++) {
RefRecord ref = vec.ptr[i];
logger::info(FMT_STRING("LoadInteriorRefList ref base_mod_name: {}, base_local_form_id: {:x}"), ref.base_mod_name, ref.base_local_form_id);
logger::info(FMT_STRING("LoadInteriorRefList ref position {:.2f} {:.2f} {:.2f}, angle: {:.2f} {:.2f} {:.2f}, scale: {:d}"), ref.position_x, ref.position_y, ref.position_z, ref.angle_x, ref.angle_y, ref.angle_z, ref.scale);
if (strcmp(ref.base_mod_name, "Skyrim.esm") == 0 && ref.base_local_form_id == 7) {
logger::info("LoadInteriorRefList skipping player ref");
continue;
}
logger::info(FMT_STRING("LoadInteriorRefList lookup form name: {}, form_id: {:x}"), form->GetName(), (uint32_t)form->GetFormID());
game_ref = PlaceAtMe_Native(a_vm, 0, target_ref, form, 1, false, false);
if (!game_ref) {
logger::error("LoadInteriorRefList failed to place new ref in cell!");
regMap.SendEvent(false);
regMap.Unregister(quest);
return false;
RE::NiPoint3 position = RE::NiPoint3::NiPoint3(ref.position_x, ref.position_y, ref.position_z);
RE::NiPoint3 angle = RE::NiPoint3::NiPoint3(ref.angle_x, ref.angle_y, ref.angle_z);
RE::TESObjectREFR * game_ref = nullptr;
if (ref.ref_mod_name) { // non-temp ref, try to lookup and reposition
game_ref = data_handler->LookupForm<RE::TESObjectREFR>(ref.ref_local_form_id, ref.ref_mod_name);
if (game_ref) {
logger::info(FMT_STRING("LoadInteriorRefList lookup ref name: {}, form_id: {:x}"), game_ref->GetName(), (uint32_t)game_ref->GetFormID());
// Failed experiment at trying to call the ObjectReference.Enable() papyrus native function
//RE::BSTSmartPointer<RE::BSScript::Object> object;
//RE::BSTSmartPointer<RE::BSScript::IStackCallbackFunctor> callback = RE::BSTSmartPointer<RE::BSScript::IStackCallbackFunctor>::BSTSmartPointer(RE::BSScript::IStackCallbackFunctor());
//a_vm->CreateObject(RE::BSFixedString("ObjectReference"), game_ref, object);
//_MESSAGE("Created Object isConstructed: %d, isInitialized: %d, isValid: %d, type: %s", object->IsConstructed(), object->IsInitialized(), object->IsValid(), object->GetTypeInfo()->GetName());
//RE::BSTHashMap<RE::VMStackID, RE::BSTSmartPointer<RE::BSScript::Stack>> runningStacks = a_vm->allRunningStacks;
//_MESSAGE("allRunningStacks size: %d", runningStacks.size());
//for (auto entry = runningStacks.begin(); entry != runningStacks.end(); ++entry) {
//_MESSAGE("allRunningStacks %d", entry->first);
//}
//a_vm->DispatchMethodCall(object, RE::BSFixedString("Enable"), RE::MakeFunctionArguments<>(), RE::BSScript::IStackCallbackFunctor::IStackCallbackFunctor())
}
else {
logger::info(FMT_STRING("LoadInteriorRefList lookup ref not found, ref_mod_name: {}, ref_local_form_id: {:x}"), ref.ref_mod_name, ref.ref_local_form_id);
}
}
if (!game_ref) { // temporary reference or non-temp ref not found, recreate from base form
RE::TESForm * form = data_handler->LookupForm(ref.base_local_form_id, ref.base_mod_name);
if (!form) { // form is not found, might be in an uninstalled mod
logger::warn(FMT_STRING("LoadInteriorRefList not spawning ref for form that could not be found in installed mods, base_mod_name: {}, base_local_form_id: {:d}"), ref.base_mod_name, ref.base_local_form_id);
continue;
}
logger::info(FMT_STRING("LoadInteriorRefList lookup form name: {}, form_id: {:x}"), form->GetName(), (uint32_t)form->GetFormID());
game_ref = PlaceAtMe_Native(a_vm, 0, target_ref, form, 1, false, false);
if (!game_ref) {
logger::error("LoadInteriorRefList failed to place new ref in cell!");
regMap.SendEvent(false);
regMap.Unregister(quest);
return false;
}
}
MoveTo_Native(game_ref, game_ref->CreateRefHandle(), cell, cell->worldSpace, position, angle);
game_ref->data.angle = angle; // set angle directly to fix bug with MoveTo in an unloaded target cell
}
RE::ObjectRefHandle handle = game_ref->CreateRefHandle();
logger::info(FMT_STRING("LoadInteriorRefList ref handle: {:x}, game_ref: {:x}"), (uint32_t)handle.get().get(), (uint32_t)game_ref);
MoveTo_Native(game_ref, handle, cell, cell->worldSpace, position, angle);
game_ref->data.angle = angle; // set angle directly to fix bug with MoveTo in an unloaded target cell
}
}
else {
const char * error = result.AsErr();
logger::error(FMT_STRING("LoadInteriorRefList get_interior_ref_list error: {}"), error);
regMap.SendEvent(false);
regMap.Unregister(quest);
return false;
}
else {
const char * error = result.AsErr();
logger::error(FMT_STRING("LoadInteriorRefList get_interior_ref_list error: {}"), error);
regMap.SendEvent(false);
regMap.Unregister(quest);
return false;
}
regMap.SendEvent(true);
regMap.Unregister(quest);
});
}
else {
logger::error("LoadInteriorRefList target_ref is null!");
regMap.SendEvent(false);
regMap.Unregister(quest);
return false;
}
@ -327,8 +330,6 @@ bool LoadInteriorRefListImpl(RE::BSFixedString api_url, RE::BSFixedString api_ke
//RE::TESObjectREFR * new_ref = func(RE::BSScript::Internal::VirtualMachine::GetSingleton(), 1, player, gold, 50, false, false);
//_MESSAGE("New ref initially disabled: %d", new_ref->IsDisabled());
//_MESSAGE("New ref persistent: %d", new_ref->loadedData->flags & RE::TESObjectREFR::RecordFlags::kPersistent);
regMap.SendEvent(true);
regMap.Unregister(quest);
return true;
}
@ -344,9 +345,9 @@ bool LoadInteriorRefList(RE::StaticFunctionTag*, RE::BSFixedString api_url, RE::
return false;
}
LoadInteriorRefListImpl(api_url, api_key, interior_ref_list_id, target_ref, quest);
// LoadInteriorRefListImpl(api_url, api_key, interior_ref_list_id, target_ref, quest);
// TODO: making this async causes a crash
// std::thread thread(LoadInteriorRefListImpl, api_url, api_key, interior_ref_list_id, target_ref, quest);
// thread.detach();
std::thread thread(LoadInteriorRefListImpl, api_url, api_key, interior_ref_list_id, target_ref, quest);
thread.detach();
return true;
}