/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Additional permissions under GNU GPL version 3 section 7 If you modify this Program, or any covered work, by linking or combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK (or a modified version of those libraries), containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the licensors of this Program grant you additional permission to convey the resulting work. Corresponding Source for a non-source form of such a combination shall include the source code for the parts of OpenSSL and IJG JPEG Library used as well as that of the covered work. You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ #include "hsTypes.h" #include "SceneSync.h" #include "SceneWatcher.h" #define MAXPLUGINCODE #include "../pnSceneObject/plSceneObject.h" #include "../MaxMain/plMaxNode.h" #include "../MaxMain/plPluginResManager.h" #include "../MaxConvert/plConvert.h" #include "../MaxConvert/hsMaterialConverter.h" #include "../MaxComponent/plComponent.h" #include "hsThread.h" #include "hsSTLStream.h" #include "../../../Plasma/Apps/plClient/plClientUpdateFormat.h" #include "plMaxFileData.h" SceneSync& SceneSync::Instance() { static SceneSync theInstance; return theInstance; } SceneSync::SceneSync() : fUpdateSignal(nil), fSceneWatcher(nil), fTimerID(0), fUpdateFreq(-1) { // Need to save the current state RegisterNotification(INotify, 0, NOTIFY_SYSTEM_PRE_RESET); RegisterNotification(INotify, 0, NOTIFY_SYSTEM_PRE_NEW); RegisterNotification(INotify, 0, NOTIFY_FILE_PRE_OPEN); RegisterNotification(INotify, 0, NOTIFY_PRE_EXPORT); // Need to load the saved state RegisterNotification(INotify, 0, NOTIFY_FILE_POST_OPEN); RegisterNotification(INotify, 0, NOTIFY_POST_EXPORT); RegisterNotification(INotify, 0, NOTIFY_EXPORT_FAILED); // Need to save the current state and cleanup RegisterNotification(INotify, 0, NOTIFY_SYSTEM_SHUTDOWN); } void SceneSync::IShutdown() { UnRegisterNotification(INotify, 0, NOTIFY_SYSTEM_PRE_RESET); UnRegisterNotification(INotify, 0, NOTIFY_SYSTEM_PRE_NEW); UnRegisterNotification(INotify, 0, NOTIFY_FILE_PRE_OPEN); UnRegisterNotification(INotify, 0, NOTIFY_PRE_EXPORT); UnRegisterNotification(INotify, 0, NOTIFY_FILE_POST_OPEN); UnRegisterNotification(INotify, 0, NOTIFY_POST_EXPORT); UnRegisterNotification(INotify, 0, NOTIFY_EXPORT_FAILED); UnRegisterNotification(INotify, 0, NOTIFY_SYSTEM_SHUTDOWN); delete fUpdateSignal; fUpdateSignal = nil; } #include #include "../MaxMain/plMaxCFGFile.h" #include "../MaxExport/plExportErrorMsg.h" // TEMP #include bool SceneSync::CreateClientData() { char path[MAX_PATH]; if (!GetOutputDir(path)) return false; char datPath[MAX_PATH]; sprintf(datPath, "%sdat", path); // Setup for the convert plExportErrorMsg msg; plConvertSettings settings; settings.fSceneViewer = true; plConvert::Instance().Init(GetCOREInterface(), &msg, &settings); // Do the convert plConvert::Instance().Convert(); // If convert failed, fail too if (msg.IsBogus()) return false; // Clear the dirty flags since everything is fresh IClearDirtyRecur((plMaxNode*)GetCOREInterface()->GetRootNode()); // // Write the converted data out and page out the objects // IDeletePath(path); CreateDirectory(path, NULL); CreateDirectory(datPath, NULL); // TEMP char oldCWD[MAX_PATH]; getcwd(oldCWD, MAX_PATH); chdir(path); hsAssert( false, "YOU NEED TO FIX ME" ); // hsgResMgr::ResMgr()->Write(); // TEMP chdir(oldCWD); IWriteNodeMap(path); // TEMP hsMaterialConverter::Instance().FreeMaterialCache(path); hsAssert( false, "YOU NEED TO FIX ME" ); // hsgResMgr::ResMgr()->PageOutConverted(); hsgResMgr::Reset(); return true; } bool SceneSync::IsClientRunning() { return (fUpdateSignal != nil); } void SceneSync::IShutdownClient() { hsNamedPipeStream outStream(hsNamedPipeStream::kThrowOnError, 500); try { if (outStream.Open(fPipeName, "w")) { // Signal the Client fUpdateSignal->Signal(); if (outStream.WaitForClientConnect()) outStream.WriteByte(ClientUpdate::kShutdown); outStream.Close(); } } catch (hsNamedPipeStream*) { hsAssert(0, "Error writing to pipe"); outStream.Close(); } } void SceneSync::SetUpdateFreq(int freq) { fUpdateFreq = freq; // If the client is running, change it's update freq if (IsClientRunning()) { // Kill the old timer if (fTimerID != 0) { KillTimer(NULL, fTimerID); fTimerID = 0; } // Create a new timer if (fUpdateFreq != -1) fTimerID = SetTimer(NULL, 0, fUpdateFreq, ITimerProc); } } bool SceneSync::BeginClientSync(const char *semaphoreName, const char *pipeName) { char path[MAX_PATH]; if (!GetOutputDir(path)) return false; char datPath[MAX_PATH]; sprintf(datPath, "%sdat", path); // Load the saved rooms and their keys (but not objects) hsAssert( false, "YOU NEED TO FIX ME" ); // hsgResMgr::ResMgr()->ForceLoadDirectory(datPath, true/*false*/); // TEMP // Set the keys in the plMaxNodes. Also, delete Plasma objects for any // plMaxNodes that can't be found (must have been deleted). IReadNodeMap(path); if (!fSceneWatcher) IStartWatching(true); if (fUpdateFreq != -1) fTimerID = SetTimer(NULL, 0, fUpdateFreq, ITimerProc); // Update(); fUpdateSignal = TRACKED_NEW hsSemaphore(0, semaphoreName); fPipeName = pipeName; return true; } void SceneSync::EndClientSync(bool abort) { if (fTimerID != 0 || fUpdateSignal) { KillTimer(NULL, fTimerID); fTimerID = 0; if (!abort) { SaveResMgr(); IShutdownClient(); } else { // Delete files so we won't try to run with this possibly corrupted data char path[MAX_PATH]; if (GetOutputDir(path)) IDeletePath(path); } delete fUpdateSignal; fUpdateSignal = nil; hsAssert( false, "YOU NEED TO FIX ME" ); // hsgResMgr::ResMgr()->PageOutConverted(); // hsgResMgr::Reset(); } } #include "../pnKeyedObject/plKey.h" void SceneSync::IClearDirtyRecur(plMaxNode *node) { node->SetDirty(plMaxNode::kAllDirty, false); for (int i = 0; i < node->NumberOfChildren(); i++) IClearDirtyRecur((plMaxNode*)node->GetChildNode(i)); } #include "../plFile/hsFiles.h" void SceneSync::IDeletePath(const char *path) { // Remove any files in the dat directory char datPath[MAX_PATH]; sprintf(datPath, "%sdat\\", path); hsFolderIterator folder(datPath); while (folder.NextFile()) { char file[MAX_PATH]; folder.GetPathAndName(file); DeleteFile(file); } // Remove the dat directory // RemoveDirectory(datPath); // Remove any files in the root dir folder.SetPath(path); while (folder.NextFile()) { char file[MAX_PATH]; folder.GetPathAndName(file); DeleteFile(file); } } bool SceneSync::SaveResMgr() { // Get the output directory for the current file char path[MAX_PATH]; if (!GetOutputDir(path)) return false; IWriteNodeMap(path); return true; } bool SceneSync::GetOutputDir(char *buf) { const char *path = plMaxConfig::GetClientPath(); if (!path) return false; const char *file = GetCOREInterface()->GetCurFileName(); if (!file || *file == '\0') return false; char filecpy[_MAX_FNAME]; _splitpath(file, nil, nil, filecpy, nil); strcpy(buf, path); strcat(buf, "SceneViewer\\"); // Make sure the SceneViewer directory is created (CreateDirectory sucks) // CreateDirectory(buf, nil); strcat(buf, filecpy); strcat(buf, "\\"); return true; } bool SceneSync::IStartWatching(bool forceWatch) { IStopWatching(); // Ref all the nodes in the scene if: // a) we are being forced to watch (starting SceneViewer) // b) there is previously saved data for this scene (we need to keep up to date) if (forceWatch || CanLoadOldResMgr()) { fSceneWatcher = TRACKED_NEW SceneWatcher; } return true; } bool SceneSync::IStopWatching() { if (!fSceneWatcher) return true; delete fSceneWatcher; fSceneWatcher = nil; return true; } static const char *kKeysFile = "NodeMap.dat"; bool SceneSync::CanLoadOldResMgr() { char path[MAX_PATH]; if (!GetOutputDir(path)) return false; strcat(path, kKeysFile); hsUNIXStream s; if (s.Open(path)) { s.Close(); return true; } return false; } static void IGetNodes(std::vector& nodes, plMaxNode *curNode=nil) { if (!curNode) curNode = (plMaxNode*)GetCOREInterface()->GetRootNode(); else nodes.push_back(curNode); for (int i = 0; i < curNode->NumberOfChildren(); i++) { plMaxNode *childNode = (plMaxNode*)curNode->GetChildNode(i); if (childNode) IGetNodes(nodes, childNode); } } //////////////////////////////////////////////////////////////////////////////// // The NodeMap is a mapping from unique node id's to Uoid's. // It is used to figure out the plKey associated with a particular node, which // will be needed if the node's data needs to be deleted out of the Plasma scene. // bool SceneSync::IWriteNodeMap(const char *dir) { char path[MAX_PATH]; strcpy(path, dir); strcat(path, kKeysFile); hsUNIXStream s; if (!s.Open(path, "wb")) return false; int numWritten = 0; s.WriteSwap32(numWritten); std::vector nodes; IGetNodes(nodes); int numNodes = nodes.size(); for (int i = 0; i < numNodes; i++) { plMaxNode *node = nodes[i]; if (node->GetKey()) { s.WriteSwap32(node->GetHandle()); node->GetKey()->GetUoid().Write(&s); numWritten++; } } s.Rewind(); s.WriteSwap32(numWritten); s.Close(); return true; } #include "../MaxMain/plMaxNodeData.h" bool SceneSync::IReadNodeMap(const char *dir) { char path[MAX_PATH]; strcpy(path, dir); strcat(path, kKeysFile); hsUNIXStream s; if (!s.Open(path, "rb")) return false; int numWritten = s.ReadSwap32(); for (int i = 0; i < numWritten; i++) { // Read in the node handle and get the actual node ULONG handle = s.ReadSwap32(); plMaxNode *node = (plMaxNode*)GetCOREInterface()->GetINodeByHandle(handle); // Read in the Uoid and get the key plUoid uoid; uoid.Read(&s); plKey key = hsgResMgr::ResMgr()->FindKey(uoid); // A node with that handle wasn't found, it must have been deleted. // Delete it from the Plasma scene. if (!node) { hsAssert( false, "YOU NEED TO FIX ME" ); // hsgResMgr::ResMgr()->RemoveObject(key); } else { // Save the node's key in the node data plMaxNodeData *dat = node->GetMaxNodeData(); // Allocate the node data if it doesn't have any if (!dat) { plMaxNodeData data; node->SetMaxNodeData(&data); dat = node->GetMaxNodeData(); } dat->SetKey(key); dat->SetSceneObject(plSceneObject::ConvertNoRef(key->GetObjectPtr())); node->CanConvert(); } } s.Close(); return true; } #include "plKeyRefSort.h" bool SceneSync::Update() { // If there are no dirty nodes, and nothing was deleted, return now if (!fSceneWatcher || (!fSceneWatcher->AnyDirty() && !fSceneWatcher->AnyDeleted())) return false; std::vector delUoids; // If any nodes were deleted, remove them from the ResManager if (fSceneWatcher->AnyDeleted()) { SceneWatcher::KeyList& deleted = fSceneWatcher->GetDeleted(); for (int i = 0; i < deleted.size(); i++) { delUoids.push_back(deleted[i]->GetUoid()); hsAssert( false, "YOU NEED TO FIX ME" ); // hsgResMgr::ResMgr()->RemoveObject(deleted[i]); } deleted.clear(); } hsAssert( false, "YOU NEED TO FIX ME" ); // hsgResMgr::ResMgr()->SaveNewKeys(true); // If any nodes are dirty, reconvert them if (fSceneWatcher->AnyDirty()) { // Go through all the referenced nodes and put all the ones that need to be // reconverted in a list SceneWatcher::NodeSet dirtyNodes; fSceneWatcher->GetDirty(dirtyNodes); // Delete the SceneObjects for all the dirty nodes, and put them in a list // that we can send to the converter hsTArray nodes; for (SceneWatcher::NodeSet::iterator it = dirtyNodes.begin(); it != dirtyNodes.end(); it++) { // If the material is dirty, tell the material converter to release // it's ref, so it will be recreated. if ((*it)->GetDirty(plMaxNode::kMatDirty)) hsMaterialConverter::Instance().ClearDoneMaterials(*it); plKey key = (*it)->GetKey(); if (key) { delUoids.push_back(key->GetUoid()); hsAssert( false, "YOU NEED TO FIX ME" ); // hsgResMgr::ResMgr()->RemoveObject(key); } nodes.Append(*it); } // Convert plExportErrorMsg msg; plConvertSettings settings; settings.fSceneViewer = true; plConvert::Instance().Init(GetCOREInterface(), &msg, &settings); hsBool ret = plConvert::Instance().Convert(nodes); // REMOVE/FIX (COLIN) hsMaterialConverter::Instance().FreeMaterialCache(nil); } // // Sort the new keys // hsAssert( false, "YOU NEED TO FIX ME" ); // const plUpdatableResManager::KeyList& keys = hsgResMgr::ResMgr()->GetNewKeys(); std::vector newKeys;// = keys; plKeyRefSort::Sort(&newKeys); #if 0 hsStatusMessage("New Keys (Sorted):\n"); for (int x = 0; x < newKeys.size(); x++) { hsStatusMessage(" "); hsStatusMessage(newKeys[x]->GetName()); hsStatusMessage("\n"); } #endif // // Write out the data to the client // hsNamedPipeStream outStream(hsNamedPipeStream::kThrowOnError); try { if (outStream.Open(fPipeName, "w")) { // Signal the Client fUpdateSignal->Signal(); if (outStream.WaitForClientConnect()) { outStream.WriteByte(ClientUpdate::kUpdate); int i; // Write out the deleted Uoids int numUoids = delUoids.size(); outStream.WriteSwap32(numUoids); for (i = 0; i < numUoids; i++) { delUoids[i].Write(&outStream); } hsAssert( false, "NEED TO FIX ME!" ); // hsgResMgr::ResMgr()->WriteChangedSpans(&outStream); // Write out the new keys (and objects) int numKeys = newKeys.size(); outStream.WriteSwap32(numKeys); for (i = 0; i < numKeys; i++) { plKey key = newKeys[i]; if (key && key->GetObjectPtr()) hsgResMgr::ResMgr()->WriteCreatable(&outStream, key->GetObjectPtr()); } } outStream.Close(); } } catch (...) { hsAssert(0, "Error writing to pipe"); outStream.Close(); EndClientSync(true); } hsAssert( false, "NEED TO FIX ME!" ); // hsgResMgr::ResMgr()->SaveNewKeys(false); return true; } void SceneSync::INotify(void *param, NotifyInfo *info) { SceneSync &inst = SceneSync::Instance(); int code = info->intcode; // Need to save the current state if (code == NOTIFY_SYSTEM_PRE_RESET || code == NOTIFY_SYSTEM_PRE_NEW || code == NOTIFY_FILE_PRE_OPEN || code == NOTIFY_PRE_EXPORT) { inst.IStopWatching(); } // Need to load the saved state else if (code == NOTIFY_FILE_POST_OPEN || code == NOTIFY_POST_EXPORT || code == NOTIFY_EXPORT_FAILED) { inst.IStartWatching(); } // Need to save the current state and cleanup else if (code == NOTIFY_SYSTEM_SHUTDOWN) { inst.SaveResMgr(); inst.IStopWatching(); inst.IShutdown(); } } void CALLBACK SceneSync::ITimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) { Instance().Update(); }