mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
4147 lines
139 KiB
C++
4147 lines
139 KiB
C++
/* Copyright 2003-2005 ROBLOX Corporation, All Rights Reserved */
|
|
#include "stdafx.h"
|
|
|
|
#include "V8DataModel/DataModel.h" // TODO - minimize these includes, and in the .h file
|
|
#include "V8DataModel/DebugSettings.h"
|
|
#include "V8DataModel/GameBasicSettings.h"
|
|
#include "V8DataModel/Workspace.h"
|
|
#include "V8DataModel/ChangeHistory.h"
|
|
#include "V8DataModel/Camera.h"
|
|
#include "V8DataModel/UserController.h"
|
|
#include "V8DataModel/KeyframeSequenceProvider.h"
|
|
#include "V8DataModel/Hopper.h"
|
|
#include "V8DataModel/Backpack.h"
|
|
#include "V8DataModel/Tool.h"
|
|
#include "V8DataModel/MouseCommand.h"
|
|
#include "V8DataModel/Teams.h"
|
|
#include "V8DataModel/Lighting.h"
|
|
#include "V8DataModel/Accoutrement.h"
|
|
#include "V8DataModel/ContentProvider.h"
|
|
#include "V8DataModel/ContextActionService.h"
|
|
#include "V8DataModel/StarterPlayerService.h"
|
|
#include "V8DataModel/ChangeHistory.h"
|
|
#include "V8DataModel/SpawnLocation.h"
|
|
#include "V8DataModel/Visit.h"
|
|
#include "V8DataModel/PlayerGui.h"
|
|
#include "V8DataModel/TextButton.h"
|
|
#include "V8DataModel/Test.h"
|
|
#include "V8DataModel/MegaCluster.h"
|
|
#include "V8datamodel/Stats.h"
|
|
#include "V8datamodel/PartInstance.h"
|
|
|
|
#include "V8DataModel/BadgeService.h"
|
|
#include "V8DataModel/ChatService.h"
|
|
#include "V8DataModel/CollectionService.h"
|
|
#include "V8DataModel/CookiesEngineService.h"
|
|
#include "V8DataModel/DebrisService.h"
|
|
#include "V8DataModel/FriendService.h"
|
|
#include "V8DataModel/GamePassService.h"
|
|
#include "V8DataModel/GeometryService.h"
|
|
#include "V8DataModel/GuiService.h"
|
|
#include "V8DataModel/ImageButton.h"
|
|
#include "V8DataModel/InsertService.h"
|
|
#include "V8DataModel/JointsService.h"
|
|
#include "V8DataModel/MarketplaceService.h"
|
|
#include "V8DataModel/PointsService.h"
|
|
#include "V8DataModel/PersonalServerService.h"
|
|
#include "V8DataModel/PhysicsService.h"
|
|
#include "V8DataModel/RenderHooksService.h"
|
|
#include "V8DataModel/ScriptService.h"
|
|
#include "V8DataModel/SocialService.h"
|
|
#include "V8DataModel/TeleportService.h"
|
|
#include "V8DataModel/UserInputService.h"
|
|
#include "V8DataModel/HackDefines.h"
|
|
#include "v8datamodel/ReplicatedStorage.h"
|
|
#include "v8datamodel/RobloxReplicatedStorage.h"
|
|
#include "v8datamodel/ReplicatedFirst.h"
|
|
#include "v8datamodel/ServerScriptService.h"
|
|
#include "v8datamodel/ServerStorage.h"
|
|
#include "v8datamodel/AssetService.h"
|
|
#include "v8datamodel/DataStoreService.h"
|
|
#include "v8datamodel/HttpService.h"
|
|
#include "V8DataModel/LogService.h"
|
|
#include "V8DataModel/AdService.h"
|
|
#include "V8DataModel/NotificationService.h"
|
|
#include "V8DataModel/CSGDictionaryService.h"
|
|
#include "V8DataModel/NonReplicatedCSGDictionaryService.h"
|
|
#include "V8DataModel/ScreenGui.h"
|
|
#include "v8datamodel/HttpRbxApiService.h"
|
|
#include "v8datamodel/GamepadService.h"
|
|
#include "v8datamodel/TextBox.h"
|
|
|
|
#include "v8datamodel/Light.h"
|
|
|
|
#include "Humanoid/Humanoid.h"
|
|
|
|
#include "Network/Api.h"
|
|
#include "Network/Player.h"
|
|
#include "Network/Players.h"
|
|
#include "Network/NetworkOwner.h"
|
|
#include "Network/GameConfigurer.h"
|
|
|
|
#include "V8World/Contact.h"
|
|
#include "V8Kernel/ContactConnector.h"
|
|
#include "V8Kernel/Kernel.h"
|
|
#include "V8World/ContactManager.h" // for fastclear
|
|
|
|
#include "v8xml/WebParser.h"
|
|
|
|
#include "Script/luainstancebridge.h"
|
|
#include "Script/scriptcontext.h"
|
|
#include "Script/Script.h"
|
|
#include "Script/CoreScript.h"
|
|
|
|
#include "Util/Statistics.h"
|
|
#include "Util/ScriptInformationProvider.h"
|
|
#include "Util/SimSendFilter.h"
|
|
#include "Util/SoundWorld.h"
|
|
#include "Util/SoundService.h"
|
|
#include "Util/StandardOut.h"
|
|
#include "Util/UserInputBase.h"
|
|
#include "Util/http.h"
|
|
#include "Util/profiling.h"
|
|
#include "Util/IMetric.h"
|
|
#include "Util/Statistics.h"
|
|
#include "Util/ContentFilter.h"
|
|
#include "Util/RobloxGoogleAnalytics.h"
|
|
|
|
#include "rbx/Log.h"
|
|
#include "FastLog.h"
|
|
#include "rbx/threadsafe.h"
|
|
#include "rbx/Countable.h"
|
|
|
|
#include "AppDraw/DrawPrimitives.h"
|
|
|
|
#include "g3d/format.h"
|
|
|
|
#include "RbxG3D/RbxTime.h"
|
|
|
|
#include "V8Xml/Serializer.h"
|
|
#include "V8Xml/XmlSerializer.h"
|
|
#include "boost/cast.hpp"
|
|
#include "boost/scoped_ptr.hpp"
|
|
|
|
#include "GfxBase/IAdornableCollector.h"
|
|
#include <fstream>
|
|
#include "rbx/RbxDbgInfo.h"
|
|
#include <boost/format.hpp>
|
|
|
|
#include "util/RbxStringTable.h"
|
|
#include "format_string.h"
|
|
#include "VMProtectSDK.h"
|
|
|
|
#include "RbxFormat.h"
|
|
|
|
#include "util/MemoryStats.h"
|
|
|
|
#include "../NetworkSettings.h"
|
|
|
|
#include "boost/algorithm/string/split.hpp"
|
|
#include "boost/algorithm/string/classification.hpp"
|
|
|
|
#include "rbx/Profiler.h"
|
|
|
|
LOGGROUP(CloseDataModel)
|
|
LOGGROUP(LegacyLock)
|
|
|
|
FASTFLAGVARIABLE(UserBetterInertialScrolling, false)
|
|
|
|
DYNAMIC_FASTINT(SavePlacePerMinute)
|
|
|
|
DYNAMIC_FASTINTVARIABLE(OnCloseTimeoutInSeconds, 30)
|
|
DYNAMIC_FASTFLAGVARIABLE(AllowHideHudShortcut, false)
|
|
DYNAMIC_FASTFLAGVARIABLE(AllowHideHudShortcutDefault, true)
|
|
DYNAMIC_FASTFLAGVARIABLE(ProcessAcceleratorsBeforeGUINavigation, false)
|
|
DYNAMIC_FASTFLAGVARIABLE(DontProcessMouseEventsForGuiTarget, false)
|
|
DYNAMIC_FASTFLAGVARIABLE(CloseStatesBeforeChildRemoval, false)
|
|
DYNAMIC_FASTFLAG(MaterialPropertiesEnabled);
|
|
DYNAMIC_FASTFLAGVARIABLE(DataModelProcessHttpRequestResponseOnLockUseSubmitTask, true)
|
|
|
|
FASTFLAGVARIABLE(RelativisticCameraFixEnable, true)
|
|
FASTFLAG(UserAllCamerasInLua)
|
|
|
|
LOGVARIABLE(CyclicExecutiveTiming, 0)
|
|
|
|
LOGVARIABLE(CyclicExecutiveThrottling, 0)
|
|
|
|
DYNAMIC_FASTFLAG(UseR15Character)
|
|
DYNAMIC_LOGVARIABLE(R15Character, 0)
|
|
|
|
namespace RBX {
|
|
|
|
static void dummyLoader(RBX::DataModel*) {}
|
|
int DataModel::LegacyLock::mainThreadId = ~0;
|
|
|
|
// DataModel::throttleAt30Fps should only affect the frequency at which Physics and Rendering runs. Nothing else.
|
|
bool DataModel::throttleAt30Fps = true; // for debugging/benchmarking - default is true;
|
|
unsigned int DataModel::sendStats = 0; // flipped if client is "hacking"
|
|
std::string DataModel::hash; // contains the hash of the exe
|
|
boost::function<void(RBX::DataModel*)> DataModel::loaderFunc = boost::function<void(RBX::DataModel*)>(dummyLoader);
|
|
|
|
REFLECTION_BEGIN();
|
|
static Reflection::BoundFuncDesc<DataModel, void()> func_loadPlugins(&DataModel::loadPlugins, "LoadPlugins", Security::Roblox);
|
|
|
|
Reflection::EventDesc<DataModel, void(shared_ptr<Instance>, const Reflection::PropertyDescriptor*)> event_ItemChanged(&DataModel::itemChangedSignal, "ItemChanged", "object", "descriptor");
|
|
|
|
#if defined(RBX_STUDIO_BUILD) || defined (RBX_RCC_SECURITY) || defined (RBX_TEST_BUILD)
|
|
static Reflection::BoundFuncDesc<DataModel, shared_ptr<const Instances>(ContentId)> getContentFunctionOld(&DataModel::fetchAsset, "get", "url", Security::LocalUser);
|
|
static Reflection::BoundFuncDesc<DataModel, shared_ptr<const Instances>(ContentId)> getContentFunction(&DataModel::fetchAsset, "GetObjects", "url", Security::Plugin);
|
|
#endif
|
|
|
|
static const Reflection::BoundYieldFuncDesc<DataModel, bool(Instance::SaveFilter)> savePlaceAsyncFunction(&DataModel::savePlaceAsync, "SavePlace", "saveFilter", DataModel::SAVE_ALL, Security::None);
|
|
|
|
static Reflection::BoundFuncDesc<DataModel, void(int)> loadWorldFunction(&DataModel::loadWorld, "LoadWorld", "assetID", Security::LocalUser);
|
|
static Reflection::BoundFuncDesc<DataModel, void(int)> loadGameFunction(&DataModel::loadGame, "LoadGame", "assetID", Security::LocalUser);
|
|
|
|
static Reflection::BoundFuncDesc<DataModel, void(ContentId)> loadFunction(&DataModel::loadContent, "Load", "url", Security::LocalUser);
|
|
static Reflection::BoundFuncDesc<DataModel, void(ContentId)> saveFunction(&DataModel::save, "Save", "url", Security::Roblox);
|
|
|
|
static Reflection::BoundFuncDesc<DataModel, void(bool)> setRemoteBuildMode(&DataModel::setRemoteBuildMode, "SetRemoteBuildMode", "buildModeEnabled", Security::LocalUser);
|
|
static Reflection::BoundFuncDesc<DataModel, bool()> getRemoteBuildMode(&DataModel::getRemoteBuildMode, "GetRemoteBuildMode", Security::None);
|
|
static Reflection::BoundFuncDesc<DataModel, void(std::string)> setServerSaveUrlFunction(&DataModel::setServerSaveUrl, "SetServerSaveUrl", "url", Security::LocalUser);
|
|
static Reflection::BoundFuncDesc<DataModel, void()> serverSaveFunction(&DataModel::serverSave, "ServerSave", Security::LocalUser);
|
|
|
|
// TODO: Security: Make this Trusted only:
|
|
// TODO: Remove the default value for synchronous, or make it true? It is non-intuitive to be false by default
|
|
static const Security::Permissions kHttpPermission = Security::RobloxScript;
|
|
static Reflection::BoundYieldFuncDesc<DataModel, std::string(std::string)> httpGetAsyncFunction(&DataModel::httpGetAsync, "HttpGetAsync", "url", kHttpPermission);
|
|
static Reflection::BoundYieldFuncDesc<DataModel, std::string(std::string, std::string, std::string)> httpPostAsyncFunction(&DataModel::httpPostAsync, "HttpPostAsync", "url", "data", "contentType", "*/*", kHttpPermission);
|
|
static Reflection::BoundFuncDesc<DataModel, std::string(std::string, bool)> httpGetFunction(&DataModel::httpGet, "HttpGet", "url", "synchronous", false, kHttpPermission);
|
|
static Reflection::BoundFuncDesc<DataModel, std::string(std::string, std::string, bool, std::string)> httpPostFunction(&DataModel::httpPost, "HttpPost", "url", "data", "synchronous", false, "contentType", "*/*", kHttpPermission);
|
|
|
|
static Reflection::BoundFuncDesc<DataModel, shared_ptr<const Reflection::ValueArray>()> getJobsInfo(&DataModel::getJobsInfo, "GetJobsInfo", Security::Plugin);
|
|
static Reflection::BoundFuncDesc<DataModel, void(std::string, std::string, std::string, std::string, std::string)> func_reportMeasurement(&DataModel::reportMeasurement, "ReportMeasurement", "id", "key1", "value1", "key2", "value2", Security::RobloxScript);
|
|
static Reflection::BoundFuncDesc<DataModel, void(std::string, std::string, std::string, int)> googleAnalyticsFunction(&DataModel::luaReportGoogleAnalytics, "ReportInGoogleAnalytics", "category", "action", "custom", "label", "none", "value", 0, Security::RobloxScript);
|
|
|
|
#if defined(RBX_STUDIO_BUILD) || defined (RBX_RCC_SECURITY) || defined (RBX_TEST_BUILD)
|
|
static Reflection::BoundFuncDesc<DataModel, void(bool)> sanitizeFunction(&DataModel::clearContents, "ClearContent", "resettingSimulation", Security::LocalUser);
|
|
#endif
|
|
static Reflection::BoundFuncDesc<DataModel, void()> closeFunction(&DataModel::close, "Shutdown", Security::LocalUser);
|
|
static Reflection::BoundFuncDesc<DataModel, void()> toggleFunction(&DataModel::toggleToolsOff, "ToggleTools", Security::LocalUser);
|
|
|
|
static Reflection::PropDescriptor<DataModel, bool> prop_isPersonalServer("IsPersonalServer", category_State, &DataModel::getIsPersonalServer, &DataModel::setIsPersonalServer, Reflection::PropertyDescriptor::SCRIPTING, Security::RobloxScript);
|
|
static Reflection::PropDescriptor<DataModel, bool> prop_canSaveLocal("LocalSaveEnabled", category_State, &DataModel::canSaveLocal, NULL, Reflection::PropertyDescriptor::UI, Security::RobloxScript);
|
|
|
|
|
|
static Reflection::BoundYieldFuncDesc<DataModel, bool()> saveToRobloxFunction(&DataModel::saveToRoblox, "SaveToRoblox", Security::RobloxScript);
|
|
static Reflection::BoundCallbackDesc<bool()> requestShutdownCallback("RequestShutdown", &DataModel::requestShutdownCallback, Security::RobloxScript);
|
|
static Reflection::BoundFuncDesc<DataModel, void(bool)> finishShutdownFunction(&DataModel::completeShutdown, "FinishShutdown", "localSave", Security::RobloxScript);
|
|
|
|
static Reflection::BoundFuncDesc<DataModel, void(std::string)> func_SetUiMessage(&DataModel::setUiMessage, "SetMessage", "message", Security::LocalUser);
|
|
static Reflection::BoundFuncDesc<DataModel, void()> func_ClearUIMessage(&DataModel::clearUiMessage, "ClearMessage", Security::LocalUser);
|
|
static Reflection::BoundFuncDesc<DataModel, void()> func_SetUIMessageBrickCount(&DataModel::setUiMessageBrickCount, "SetMessageBrickCount", Security::LocalUser);
|
|
static Reflection::BoundFuncDesc<DataModel, std::string()> func_GetUIMessage(&DataModel::getUpdatedMessageBoxText, "GetMessage", Security::None);
|
|
|
|
static Reflection::BoundFuncDesc<DataModel, shared_ptr<const Reflection::ValueArray>()> getJobsExtendedStats(&DataModel::getJobsExtendedStats, "GetJobsExtendedStats", Security::Plugin);
|
|
static Reflection::BoundFuncDesc<DataModel, double(std::string, double)> getJobTimePeakFraction(&DataModel::getJobTimePeakFraction, "GetJobTimePeakFraction", "jobname", "greaterThan", Security::Plugin);
|
|
static Reflection::BoundFuncDesc<DataModel, double(std::string, double)> getJobIntervalPeakFraction(&DataModel::getJobIntervalPeakFraction, "GetJobIntervalPeakFraction", "jobname", "greaterThan", Security::Plugin);
|
|
// TODO: Make this part of settings, not per-document. After all, it is a global setting
|
|
static Reflection::BoundFuncDesc<DataModel, void(double)> setJobsExtendedStatsWindow(&DataModel::setJobsExtendedStatsWindow, "SetJobsExtendedStatsWindow", "seconds", Security::LocalUser);
|
|
|
|
static Reflection::BoundFuncDesc<DataModel, void(int)> setPlaceVersion(&DataModel::setPlaceVersion, "SetPlaceVersion", "placeId", Security::Plugin);
|
|
static Reflection::BoundFuncDesc<DataModel, void(int, bool)> setPlaceId(&DataModel::setPlaceID, "SetPlaceId", "placeId", "robloxPlace", false, Security::Plugin);
|
|
static Reflection::BoundFuncDesc<DataModel, void(int, bool)> setPlaceIdDeprecated(&DataModel::setPlaceID, "SetPlaceID", "placeID", "robloxPlace", false, Security::Plugin);
|
|
static Reflection::BoundFuncDesc<DataModel, void(std::string)> setGameInstanceId(&DataModel::setGameInstanceID, "SetGameInstanceId", "instanceID", Security::Plugin);
|
|
static Reflection::BoundFuncDesc<DataModel, void(int)> setUniverseId(&DataModel::setUniverseId, "SetUniverseId", "universeId", Security::Plugin);
|
|
static Reflection::BoundFuncDesc<DataModel, void(int, DataModel::CreatorType)> setCreatorId(&DataModel::setCreatorID, "SetCreatorId", "creatorId", "creatorType", Security::Plugin);
|
|
static Reflection::BoundFuncDesc<DataModel, void(int, DataModel::CreatorType)> setCreatorIdDeprecated(&DataModel::setCreatorID, "SetCreatorID", "creatorID", "creatorType", Security::Plugin);
|
|
static Reflection::BoundFuncDesc<DataModel, void(DataModel::Genre)> func_setGenre(&DataModel::setGenre, "SetGenre", "genre", Security::Plugin);
|
|
static Reflection::BoundFuncDesc<DataModel, void(DataModel::GearGenreSetting, int)> func_setGear(&DataModel::setGear, "SetGearSettings", "genreRestriction", "allowedGenres", Security::Plugin);
|
|
|
|
static Reflection::RefPropDescriptor<DataModel, Workspace> prop_Workspace("Workspace", category_Data, &DataModel::getWorkspace, NULL, Reflection::PropertyDescriptor::UI);
|
|
static Reflection::RefPropDescriptor<DataModel, Workspace> prop_workspace("workspace", category_Data, &DataModel::getWorkspace, NULL, Reflection::PropertyDescriptor::Attributes::deprecated(prop_Workspace));
|
|
Reflection::RefPropDescriptor<DataModel, Instance> DataModel::prop_lighting("lighting", category_Data, &DataModel::getLightingDeprecated, NULL, Reflection::PropertyDescriptor::Attributes::deprecated());
|
|
|
|
static Reflection::PropDescriptor<DataModel, int> prop_placeId("PlaceId", category_State, &DataModel::getPlaceID, NULL, Reflection::PropertyDescriptor::UI);
|
|
static Reflection::PropDescriptor<DataModel, int> prop_placeVersion("PlaceVersion", category_State, &DataModel::getPlaceVersion, NULL, Reflection::PropertyDescriptor::UI);
|
|
static Reflection::PropDescriptor<DataModel, int> prop_creatorId("CreatorId", category_State, &DataModel::getCreatorID, NULL, Reflection::PropertyDescriptor::UI);
|
|
static Reflection::PropDescriptor<DataModel, bool> prop_forceR15("ForceR15", category_State, &DataModel::getForceR15, &DataModel::setForceR15, Reflection::PropertyDescriptor::REPLICATE_ONLY, Security::Roblox);
|
|
static Reflection::EnumPropDescriptor<DataModel, DataModel::CreatorType> prop_creatorType("CreatorType", category_State, &DataModel::getCreatorType, NULL, Reflection::PropertyDescriptor::UI);
|
|
static Reflection::EnumPropDescriptor<DataModel, DataModel::Genre> prop_genre("Genre", category_State, &DataModel::getGenre, NULL, Reflection::PropertyDescriptor::UI);
|
|
static Reflection::EnumPropDescriptor<DataModel, DataModel::GearGenreSetting> prop_gearGenreSetting("GearGenreSetting", category_State, &DataModel::getGearGenreSetting, NULL, Reflection::PropertyDescriptor::UI);
|
|
static Reflection::BoundFuncDesc<DataModel, bool(DataModel::GearType)> func_isGearTypeAllowed(&DataModel::isGearTypeAllowed, "IsGearTypeAllowed", "gearType", Security::None);
|
|
static Reflection::EventDesc<DataModel, void()> event_allowedGearTypeChanged(&DataModel::allowedGearTypeChanged, "AllowedGearTypeChanged");
|
|
|
|
static Reflection::PropDescriptor<DataModel, std::string> prop_getJobId("JobId", "JobInfo", &DataModel::getJobId, NULL, Reflection::PropertyDescriptor::UI);
|
|
|
|
static Reflection::BoundFuncDesc<DataModel, void(std::string)> setScreenshotSEOInfoFunction(&DataModel::setScreenshotSEOInfo, "SetScreenshotInfo", "info", Security::LocalUser);
|
|
static Reflection::BoundFuncDesc<DataModel, void(std::string)> setVideoSEOInfoFunction(&DataModel::setVideoSEOInfo, "SetVideoInfo", "info", Security::LocalUser);
|
|
|
|
static Reflection::EventDesc<DataModel, void(bool)> event_graphicsQualityChangeRequest(&DataModel::graphicsQualityShortcutSignal, "GraphicsQualityChangeRequest","betterQuality");
|
|
|
|
static Reflection::EventDesc<DataModel, void()> event_GameLoaded(&DataModel::gameLoadedSignal, "Loaded");
|
|
static Reflection::BoundFuncDesc<DataModel, bool()> gameLoadedFunction(&DataModel::getIsGameLoaded, "IsLoaded", Security::None);
|
|
|
|
static Reflection::BoundFuncDesc<DataModel, void(std::string, std::string)> addStatFunction(&DataModel::addCustomStat, "AddStat", "displayName", "stat", Security::LocalUser);
|
|
static Reflection::BoundFuncDesc<DataModel, void(std::string)> removeStatFunction(&DataModel::removeCustomStat, "RemoveStat", "stat", Security::LocalUser);
|
|
static Reflection::BoundFuncDesc<DataModel, void()> writeStatSettingsFunction(&DataModel::writeStatsSettings, "SaveStats", Security::LocalUser);
|
|
|
|
static Reflection::BoundAsyncCallbackDesc<
|
|
DataModel, shared_ptr<const Reflection::Tuple>(void)> callback_onClose("OnClose", &DataModel::onCloseCallback, &DataModel::onCloseCallbackChanged);
|
|
|
|
static Reflection::PropDescriptor<DataModel, std::string> desc_VIPServerId("VIPServerId", category_Data, &DataModel::getVIPServerId, NULL);
|
|
static Reflection::BoundFuncDesc<DataModel, void(std::string)> func_setVIPServerId(&DataModel::setVIPServerId, "SetVIPServerId", "newId", Security::LocalUser);
|
|
static Reflection::PropDescriptor<DataModel, int> desc_VIPServerOwnerId("VIPServerOwnerId", category_Data, &DataModel::getVIPServerOwnerId, NULL);
|
|
static Reflection::BoundFuncDesc<DataModel, void(int)> func_setVIPServerOwnerId(&DataModel::setVIPServerOwnerId, "SetVIPServerOwnerId", "newId", Security::LocalUser);
|
|
REFLECTION_END();
|
|
|
|
bool DataModel::BlockingDataModelShutdown = true;
|
|
unsigned int DataModel::perfStats = 0; // flipped if client is "hacking"
|
|
|
|
const char *const sDataModel = "DataModel";
|
|
|
|
namespace Reflection
|
|
{
|
|
|
|
|
|
template<>
|
|
EnumDesc<DataModel::CreatorType>::EnumDesc()
|
|
:EnumDescriptor("CreatorType")
|
|
{
|
|
addPair(DataModel::CREATOR_USER, "User");
|
|
addPair(DataModel::CREATOR_GROUP, "Group");
|
|
}
|
|
|
|
template<>
|
|
DataModel::CreatorType& Variant::convert<DataModel::CreatorType>(void)
|
|
{
|
|
return genericConvert<DataModel::CreatorType>();
|
|
}
|
|
|
|
|
|
template<>
|
|
EnumDesc<DataModel::Genre>::EnumDesc()
|
|
:EnumDescriptor("Genre")
|
|
{
|
|
addPair(DataModel::GENRE_ALL , "All");
|
|
addPair(DataModel::GENRE_TOWN_AND_CITY , "TownAndCity");
|
|
addPair(DataModel::GENRE_FANTASY , "Fantasy");
|
|
addPair(DataModel::GENRE_SCI_FI , "SciFi");
|
|
addPair(DataModel::GENRE_NINJA , "Ninja");
|
|
addPair(DataModel::GENRE_SCARY , "Scary");
|
|
addPair(DataModel::GENRE_PIRATE , "Pirate");
|
|
addPair(DataModel::GENRE_ADVENTURE , "Adventure");
|
|
addPair(DataModel::GENRE_SPORTS , "Sports");
|
|
addPair(DataModel::GENRE_FUNNY , "Funny");
|
|
addPair(DataModel::GENRE_WILD_WEST , "WildWest");
|
|
addPair(DataModel::GENRE_WAR , "War");
|
|
addPair(DataModel::GENRE_SKATE_PARK , "SkatePark");
|
|
addPair(DataModel::GENRE_TUTORIAL , "Tutorial");
|
|
}
|
|
|
|
template<>
|
|
DataModel::Genre& Variant::convert<DataModel::Genre>(void)
|
|
{
|
|
return genericConvert<DataModel::Genre>();
|
|
}
|
|
|
|
|
|
template<>
|
|
EnumDesc<DataModel::GearGenreSetting>::EnumDesc()
|
|
:EnumDescriptor("GearGenreSetting")
|
|
{
|
|
addPair(DataModel::GEAR_GENRE_ALL, "AllGenres");
|
|
addPair(DataModel::GEAR_GENRE_MATCH, "MatchingGenreOnly");
|
|
}
|
|
|
|
template<>
|
|
DataModel::GearGenreSetting& Variant::convert<DataModel::GearGenreSetting>(void)
|
|
{
|
|
return genericConvert<DataModel::GearGenreSetting>();
|
|
}
|
|
|
|
|
|
template<>
|
|
EnumDesc<DataModel::GearType>::EnumDesc()
|
|
:EnumDescriptor("GearType")
|
|
{
|
|
addPair(DataModel::GEAR_TYPE_MELEE_WEAPONS , "MeleeWeapons");
|
|
addPair(DataModel::GEAR_TYPE_RANGED_WEAPONS , "RangedWeapons");
|
|
addPair(DataModel::GEAR_TYPE_EXPLOSIVES , "Explosives");
|
|
addPair(DataModel::GEAR_TYPE_POWER_UPS , "PowerUps");
|
|
addPair(DataModel::GEAR_TYPE_NAVIGATION_ENHANCERS , "NavigationEnhancers");
|
|
addPair(DataModel::GEAR_TYPE_MUSICAL_INSTRUMENTS , "MusicalInstruments");
|
|
addPair(DataModel::GEAR_TYPE_SOCIAL_ITEMS , "SocialItems");
|
|
addPair(DataModel::GEAR_TYPE_BUILDING_TOOLS , "BuildingTools");
|
|
addPair(DataModel::GEAR_TYPE_PERSONAL_TRANSPORT , "Transport");
|
|
}
|
|
|
|
template<>
|
|
DataModel::GearType& Variant::convert<DataModel::GearType>(void)
|
|
{
|
|
return genericConvert<DataModel::GearType>();
|
|
}
|
|
|
|
template<>
|
|
EnumDesc<Instance::SaveFilter>::EnumDesc()
|
|
:EnumDescriptor("SaveFilter")
|
|
{
|
|
addPair(Instance::SAVE_ALL , "SaveAll");
|
|
addPair(Instance::SAVE_WORLD , "SaveWorld");
|
|
addPair(Instance::SAVE_GAME , "SaveGame");
|
|
}
|
|
|
|
template<>
|
|
Instance::SaveFilter& Variant::convert<Instance::SaveFilter>(void)
|
|
{
|
|
return genericConvert<Instance::SaveFilter>();
|
|
}
|
|
}
|
|
|
|
template<>
|
|
bool RBX::StringConverter<DataModel::CreatorType>::convertToValue(const std::string& text, DataModel::CreatorType& value)
|
|
{
|
|
return Reflection::EnumDesc<DataModel::CreatorType>::singleton().convertToValue(text.c_str(),value);
|
|
}
|
|
|
|
template<>
|
|
bool RBX::StringConverter<DataModel::Genre>::convertToValue(const std::string& text, DataModel::Genre& value)
|
|
{
|
|
return Reflection::EnumDesc<DataModel::Genre>::singleton().convertToValue(text.c_str(),value);
|
|
}
|
|
|
|
template<>
|
|
bool RBX::StringConverter<DataModel::GearGenreSetting>::convertToValue(const std::string& text, DataModel::GearGenreSetting& value)
|
|
{
|
|
return Reflection::EnumDesc<DataModel::GearGenreSetting>::singleton().convertToValue(text.c_str(),value);
|
|
}
|
|
|
|
template<>
|
|
bool RBX::StringConverter<DataModel::GearType>::convertToValue(const std::string& text, DataModel::GearType& value)
|
|
{
|
|
return Reflection::EnumDesc<DataModel::GearType>::singleton().convertToValue(text.c_str(),value);
|
|
}
|
|
|
|
template<>
|
|
bool RBX::StringConverter<DataModel::SaveFilter>::convertToValue(const std::string& text, DataModel::SaveFilter& value)
|
|
{
|
|
return Reflection::EnumDesc<DataModel::SaveFilter>::singleton().convertToValue(text.c_str(),value);
|
|
}
|
|
|
|
|
|
class DataModel::GenericJob : public DataModelJob
|
|
{
|
|
public:
|
|
|
|
weak_ptr<DataModel> const dataModel;
|
|
rbx::timestamped_safe_queue<Task> tasks;
|
|
|
|
GenericJob(shared_ptr<DataModel> dataModel, const char* name, TaskType taskType)
|
|
:DataModelJob(name, taskType, false, dataModel, Time::Interval(0))
|
|
,dataModel(dataModel)
|
|
{
|
|
}
|
|
private:
|
|
virtual Time::Interval sleepTime(const Stats& stats)
|
|
{
|
|
return tasks.size() > 0 ? Time::Interval(0) : Time::Interval::max();
|
|
}
|
|
|
|
void step(Task& task)
|
|
{
|
|
shared_ptr<DataModel> dm(dataModel.lock());
|
|
if (!dm)
|
|
return;
|
|
|
|
task(dm.get());
|
|
}
|
|
|
|
//
|
|
// *** THIS IS BEING USED AS A NEAR *IMMEDIATE* CALLBACK BY THE LEGACY LOCK. ***
|
|
// *** HOWEVER THE ERROR CALCUALTION IS GARBAGE FOR THAT, INITIALLY RETURNING ZERO. ***
|
|
//
|
|
virtual Job::Error error(const Stats& stats)
|
|
{
|
|
Error result;
|
|
|
|
const int size = tasks.size();
|
|
if (size)
|
|
{
|
|
// Estimate the total error to be headTime * n / 2
|
|
result.error = tasks.head_waittime_sec(stats.timeNow) * (1 + size) / 2.0;
|
|
|
|
// These tasks are "urgent" because they are often used
|
|
// with LegacyLock to marshal tasks with the UI or Web Service requests
|
|
result.urgent = size > 0;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
virtual TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
|
|
{
|
|
FASTLOG3(FLog::TaskSchedulerRun, "GenericJob::Step dataModel(%d) type(%d) taskCount(%d)", dataModel.lock().get(), taskType, tasks.size());
|
|
|
|
switch (taskType)
|
|
{
|
|
case Write:
|
|
case DataIn:
|
|
case PhysicsIn:
|
|
{
|
|
shared_ptr<DataModel> dm(dataModel.lock());
|
|
if (!dm)
|
|
return TaskScheduler::Done;
|
|
// TODO: clean up, non-scoped write request?
|
|
DataModel::scoped_write_request request(dm.get());
|
|
|
|
// need to call processTasks here because of the scoped write request
|
|
processTasks();
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
processTasks();
|
|
}
|
|
break;
|
|
}
|
|
return TaskScheduler::Stepped;
|
|
}
|
|
|
|
void processTasks()
|
|
{
|
|
FASTLOG(FLog::DataModelJobs, "Process tasks start");
|
|
Task task;
|
|
int size = tasks.size();
|
|
int count = 0;
|
|
while ((count < size) && (tasks.pop_if_present(task))) // need to keep count because task can add more tasks
|
|
{
|
|
step(task);
|
|
count++;
|
|
|
|
if (TaskScheduler::singleton().isCyclicExecutive())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
RBX::Time timeNow = RBX::Time::now<RBX::Time::Fast>();
|
|
if (TaskScheduler::priorityMethod != TaskScheduler::FIFO)
|
|
break;
|
|
else if (tasks.head_waittime_sec(timeNow) < 0.2)
|
|
{
|
|
// FIFO mode: if we are able to process incoming tasks in a timely manner, process remaining tasks next frame
|
|
break;
|
|
}
|
|
}
|
|
FASTLOG(FLog::DataModelJobs, "Process tasks finish");
|
|
}
|
|
};
|
|
|
|
|
|
static std::string tempTag()
|
|
{
|
|
static rbx::atomic<int> count = 0;
|
|
return RBX::format("DataModel-%d", (int)++count);
|
|
}
|
|
|
|
bool DataModel::canSave(const RBX::Instance* instance)
|
|
{
|
|
//If the UploadURL is empty, they don't have permiissions to save. Shut. It. Down.
|
|
if(Visit* visit = ServiceProvider::find<Visit>(instance))
|
|
return !visit->getUploadUrl().empty();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool DataModel::serverSavePlace(const SaveFilter saveFilter, boost::function<void(bool)> resumeFunction, boost::function<void(std::string)> errorFunction)
|
|
{
|
|
// construct the url
|
|
std::string parameters = "?assetId=" + boost::lexical_cast<std::string>(placeID);
|
|
parameters += "&isAppCreation=true";
|
|
std::string baseUrl = ServiceProvider::create<ContentProvider>(this)->getBaseUrl();
|
|
|
|
// save the place
|
|
return uploadPlace(baseUrl + "ide/publish/UploadExistingAsset" + parameters, saveFilter, boost::bind(resumeFunction, true), errorFunction);
|
|
}
|
|
|
|
namespace {
|
|
static void sendSavePlaceStats()
|
|
{
|
|
RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_GAME, "SavePlace");
|
|
}
|
|
} // namespace
|
|
|
|
void DataModel::savePlaceAsync(const SaveFilter saveFilter, boost::function<void(bool)> resumeFunction, boost::function<void(std::string)> errorFunction)
|
|
{
|
|
if (placeID <= 0)
|
|
{
|
|
errorFunction("Game:SavePlace placeID is not valid!");
|
|
return;
|
|
}
|
|
|
|
if(!savePlaceThrottle.checkLimit())
|
|
{
|
|
errorFunction("Game:SavePlace requests limit reached");
|
|
return;
|
|
}
|
|
|
|
if(Network::Players::frontendProcessing(this))
|
|
{
|
|
errorFunction("Game:SavePlace can only be called from a server script, aborting save function");
|
|
return;
|
|
}
|
|
else if(Network::Players::backendProcessing(this))
|
|
{
|
|
{
|
|
static boost::once_flag flag = BOOST_ONCE_INIT;
|
|
boost::call_once(&sendSavePlaceStats, flag);
|
|
}
|
|
serverSavePlace(saveFilter, resumeFunction, errorFunction);
|
|
|
|
}
|
|
else
|
|
{
|
|
errorFunction("Game:SavePlace could not determine if client or server");
|
|
return;
|
|
}
|
|
}
|
|
|
|
void DataModel::completeShutdown(bool saveLocal)
|
|
{
|
|
if(shutdownRequestedCount > 0){
|
|
if(saveLocal && canSave(this)){
|
|
if(Verb* verb = workspace->getWhitelistVerb("Shutdown", "Client", "AndSave")){
|
|
Verb::doItWithChecks(verb, NULL);
|
|
}
|
|
}
|
|
else{
|
|
if(Verb* verb = workspace->getWhitelistVerb("Shutdown", "Client", "")){
|
|
Verb::doItWithChecks(verb, NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DataModel::RequestShutdownResult DataModel::requestShutdown(bool useLuaShutdownForSave)
|
|
{
|
|
if(!canSave(this))
|
|
{
|
|
Verb* verb = getWorkspace()->getWhitelistVerb("Toggle", "Play", "Mode");
|
|
if ( verb && verb->isChecked() )
|
|
return CLOSE_LOCAL_SAVE;
|
|
|
|
//We are in play mode... so don't save
|
|
return CLOSE_NO_SAVE_NEEDED;
|
|
}
|
|
|
|
Visit* visit = RBX::ServiceProvider::find<Visit>(this);
|
|
if (!visit || visit->getUploadUrl().empty())
|
|
{
|
|
return CLOSE_LOCAL_SAVE;
|
|
}
|
|
|
|
if(useLuaShutdownForSave && requestShutdownCallback){
|
|
try
|
|
{
|
|
shutdownRequestedCount++;
|
|
return requestShutdownCallback() ? CLOSE_REQUEST_HANDLED : CLOSE_NOT_HANDLED;
|
|
}
|
|
catch(RBX::base_exception&)
|
|
{
|
|
return CLOSE_NOT_HANDLED;
|
|
}
|
|
}
|
|
|
|
return CLOSE_NOT_HANDLED;
|
|
}
|
|
|
|
TaskScheduler::Arbiter* DataModel::getSyncronizationArbiter()
|
|
{
|
|
return this;
|
|
}
|
|
|
|
void DataModel::doDataModelSetup(shared_ptr<DataModel> dataModel, bool startHeartbeat, bool shouldShowLoadingScreen)
|
|
{
|
|
shared_ptr<DataModel::GenericJob> j;
|
|
j = shared_ptr<DataModel::GenericJob>(new DataModel::GenericJob(dataModel, "Write Marshalled", DataModelJob::Write));
|
|
j->cyclicExecutive = false;
|
|
dataModel->genericJobs[DataModelJob::Write] = j;
|
|
TaskScheduler::singleton().add(j);
|
|
j = shared_ptr<DataModel::GenericJob>(new DataModel::GenericJob(dataModel, "Read Marshalled", DataModelJob::Read));
|
|
j->cyclicExecutive = false;
|
|
dataModel->genericJobs[DataModelJob::Read] = j;
|
|
TaskScheduler::singleton().add(j);
|
|
j = shared_ptr<DataModel::GenericJob>(new DataModel::GenericJob(dataModel, "None Marshalled", DataModelJob::None));
|
|
j->cyclicExecutive = false;
|
|
dataModel->genericJobs[DataModelJob::None] = j;
|
|
TaskScheduler::singleton().add(j);
|
|
|
|
// TODO: Do we need this lock here? If initializing has no side effects
|
|
// like starting a task, then it shouldn't be necessary.
|
|
LegacyLock lock(dataModel, DataModelJob::Write);
|
|
|
|
RBX::DataModel::LegacyLock::mainThreadId = GetCurrentThreadId();
|
|
|
|
dataModel->initializeContents(startHeartbeat);
|
|
dataModel->isInitialized = true;
|
|
dataModel->suppressNavKeys = false;
|
|
|
|
// get loading script going ASAP so it shows on first render
|
|
if (shouldShowLoadingScreen && !(TeleportService::didTeleport() && TeleportService::getCustomTeleportLoadingGui()))
|
|
{
|
|
try
|
|
{
|
|
if (boost::optional<ProtectedString> source = CoreScript::fetchSource("LoadingScript"))
|
|
{
|
|
if (ScriptContext* sc = RBX::ServiceProvider::create<ScriptContext>(dataModel.get()))
|
|
{
|
|
sc->executeInNewThread(RBX::Security::RobloxGameScript_, *source, "LoadingScript");
|
|
}
|
|
}
|
|
}
|
|
catch(std::exception& e)
|
|
{
|
|
StandardOut::singleton()->printf(MESSAGE_ERROR, "LoadingScript Error: %s", e.what());
|
|
}
|
|
}
|
|
|
|
FASTLOG1(FLog::CloseDataModel, "DataModel created: %p", dataModel.get());
|
|
}
|
|
|
|
shared_ptr<DataModel> DataModel::createDataModel(bool startHeartbeat, RBX::Verb* lockVerb, bool shouldShowLoadingScreen)
|
|
{
|
|
FASTLOG1(FLog::CloseDataModel, "Create DataModel - heartbeat(%d)", startHeartbeat);
|
|
|
|
shared_ptr<DataModel> dataModel = Creatable<Instance>::create<RBX::DataModel>(lockVerb);
|
|
|
|
doDataModelSetup(dataModel, startHeartbeat, shouldShowLoadingScreen);
|
|
return dataModel;
|
|
}
|
|
|
|
bool DataModel::onlyJobsLeftForThisArbiterAreGenericJobs() {
|
|
std::vector<boost::shared_ptr<const TaskScheduler::Job> > jobs;
|
|
TaskScheduler::singleton().getJobsInfo(jobs);
|
|
std::vector<boost::shared_ptr<const TaskScheduler::Job> >::iterator itr =
|
|
jobs.begin();
|
|
|
|
while (itr != jobs.end()) {
|
|
if ((*itr)->getArbiter().get() == this) {
|
|
bool anyMatch = false;
|
|
for (int i = 0; i < DataModelJob::TaskTypeMax && !anyMatch; ++i) {
|
|
if (genericJobs[i] == *itr) {
|
|
anyMatch = true;
|
|
}
|
|
}
|
|
RBXASSERT(anyMatch);
|
|
if (!anyMatch) {
|
|
return false;
|
|
}
|
|
}
|
|
itr++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void DataModel::doCloseDataModel(shared_ptr<DataModel> dataModel)
|
|
{
|
|
FASTLOG1(FLog::CloseDataModel, "doCloseDataModel - %p", dataModel.get());
|
|
|
|
RBX::Security::Impersonator impersonate(RBX::Security::LocalGUI_);
|
|
|
|
// Turn off ChangeHistoryService to avoid unecessary recording of state
|
|
if (ChangeHistoryService* ch = ServiceProvider::find<ChangeHistoryService>(dataModel.get()))
|
|
ch->setEnabled(false);
|
|
|
|
FASTLOG(FLog::CloseDataModel, "Raising close..");
|
|
dataModel->raiseClose(); // TODO: Demote this to MFC-only project???
|
|
|
|
RBXASSERT(dataModel->isInitialized);
|
|
dataModel->isInitialized = false;
|
|
|
|
FASTLOG(FLog::CloseDataModel, "Removing all players");
|
|
if(Network::Players* players = ServiceProvider::find<Network::Players>(dataModel.get()))
|
|
players->removeAllChildren();
|
|
|
|
FASTLOG(FLog::CloseDataModel, "Clearing contents...");
|
|
// Ensure that all content is wiped out
|
|
dataModel->clearContents(false);
|
|
|
|
|
|
FASTLOG(FLog::CloseDataModel, "Visiting children with unlockParent...");
|
|
dataModel->visitChildren(boost::bind(&Instance::unlockParent, _1));
|
|
|
|
FASTLOG(FLog::CloseDataModel, "Removing all Children...");
|
|
dataModel->removeAllChildren();
|
|
|
|
FASTLOG(FLog::CloseDataModel, "Resetting workspace...");
|
|
dataModel->workspace.reset();
|
|
|
|
FASTLOG(FLog::CloseDataModel, "Resetting runService...");
|
|
dataModel->runService.reset();
|
|
|
|
FASTLOG(FLog::CloseDataModel, "Resetting starterPackService...");
|
|
dataModel->starterPackService.reset();
|
|
|
|
FASTLOG(FLog::CloseDataModel, "Resetting starterGuiService...");
|
|
dataModel->starterGuiService.reset();
|
|
|
|
FASTLOG(FLog::CloseDataModel, "Resetting starterPlayerService...");
|
|
dataModel->starterPlayerService.reset();
|
|
|
|
FASTLOG(FLog::CloseDataModel, "Resetting guiRoot...");
|
|
dataModel->guiRoot.reset();
|
|
|
|
FASTLOG(FLog::CloseDataModel, "Clearing services...");
|
|
dataModel->clearServices();
|
|
|
|
RBXASSERT(dataModel->onlyJobsLeftForThisArbiterAreGenericJobs());
|
|
|
|
FASTLOG(FLog::CloseDataModel, "Removing GenericJobs...");
|
|
{
|
|
bool blockingRemove = DataModel::BlockingDataModelShutdown && RBX::DebugSettings::singleton().blockingRemove;
|
|
for (GenericJobs::iterator iter = dataModel->genericJobs.begin(); iter!=dataModel->genericJobs.end(); ++iter)
|
|
{
|
|
if(blockingRemove)
|
|
TaskScheduler::singleton().removeBlocking(*iter);
|
|
else
|
|
TaskScheduler::singleton().remove(*iter);
|
|
iter->reset();
|
|
}
|
|
}
|
|
|
|
FASTLOG(FLog::CloseDataModel, "Close DataModel Done!");
|
|
}
|
|
|
|
static void closeCallbackContinuation(shared_ptr<CEvent> event)
|
|
{
|
|
FASTLOG(FLog::CloseDataModel, "Close Callback finished");
|
|
FASTLOG1(FLog::CloseDataModel, "Close callback resets event - %p", event.get());
|
|
event->Set();
|
|
}
|
|
|
|
static void errorCallbackContinuation(std::string error, shared_ptr<CEvent> event)
|
|
{
|
|
FASTLOGS(FLog::CloseDataModel, "Close Callback error - %s", error);
|
|
FASTLOG1(FLog::CloseDataModel, "Close callback resets event on error - %p", event.get());
|
|
event->Set();
|
|
}
|
|
|
|
|
|
void DataModel::closeDataModel(shared_ptr<DataModel> dataModel)
|
|
{
|
|
FASTLOG1(FLog::CloseDataModel, "closeDataModel - %p", dataModel.get());
|
|
|
|
if (!dataModel)
|
|
return;
|
|
|
|
|
|
if(dataModel->onCloseCallback)
|
|
{
|
|
FASTLOG1(FLog::CloseDataModel, "Setting up onClose callback - %p", dataModel.get());
|
|
|
|
RBXASSERT(!dataModel->currentThreadHasWriteLock());
|
|
shared_ptr<CEvent> waitEvent(new CEvent(false));
|
|
|
|
{
|
|
LegacyLock lock(dataModel, DataModelJob::Write);
|
|
if(Workspace::clientIsPresent(dataModel.get()))
|
|
waitEvent->Set();
|
|
else
|
|
{
|
|
FASTLOG(FLog::CloseDataModel, "Calling onClose callback");
|
|
dataModel->onCloseCallback(boost::bind(closeCallbackContinuation, waitEvent), boost::bind(errorCallbackContinuation, _1, waitEvent));
|
|
}
|
|
}
|
|
|
|
bool result = waitEvent->Wait(Time::Interval((double)DFInt::OnCloseTimeoutInSeconds));
|
|
FASTLOG1(FLog::CloseDataModel, "Wait returned %u", result);
|
|
}
|
|
|
|
LegacyLock lock(dataModel, DataModelJob::Write);
|
|
FASTLOG(FLog::CloseDataModel, "Finished waiting on lock");
|
|
doCloseDataModel(dataModel);
|
|
}
|
|
|
|
void DataModel::onCloseCallbackChanged(const CloseCallback& oldValue)
|
|
{
|
|
if(onCloseCallback && oldValue)
|
|
{
|
|
onCloseCallback = oldValue;
|
|
throw RBX::runtime_error("OnClose is already set");
|
|
}
|
|
}
|
|
|
|
// TODO: Refactor: This class is HORRIBLE. GET RID OF THE BLOB
|
|
// Also, it is dangerous to initialize all this stuff in the contructor
|
|
// Because DataModel isn't owned by a shared_ptr yet. So, code that
|
|
// calls shared_from(dataModel) will fail
|
|
// My only idea at the moment to get around it is to have a DataModel::initializeContet
|
|
// function, or perhaps wrap the whole thing into a static "CreateDataModel" function
|
|
// that constructs DataModel, and creates essential services.
|
|
// All these verbs should be taken out and put into a UI library (which isn't needed by
|
|
// by the WebService, for example.
|
|
DataModel::DataModel(RBX::Verb* lockVerb)
|
|
:Super("Game"),
|
|
VerbContainer(NULL),
|
|
shutdownRequestedCount(0),
|
|
lockVerb(lockVerb),
|
|
write_requested(0),
|
|
writeRequestingThread(0),
|
|
read_requested(0),
|
|
dirty(false),
|
|
isInitialized(false),
|
|
isContentLoaded(false),
|
|
isShuttingDown(false),
|
|
runningInStudio(false),
|
|
isStudioRunMode(false),
|
|
checkedExperimentalFeatures(false),
|
|
drawId(0),
|
|
placeID(0),
|
|
gameInstanceID(""),
|
|
placeVersion(0),
|
|
creatorID(0),
|
|
universeId(0),
|
|
physicsStepID(0),
|
|
totalWorldSteps(0),
|
|
creatorType(DataModel::CREATOR_USER),
|
|
remoteBuildMode(false),
|
|
numPartInstances(0),
|
|
numInstances(0),
|
|
mouseOverGui(false),
|
|
networkMetric(NULL),
|
|
#pragma warning(push)
|
|
#pragma warning(disable: 4355) // 'this' : used in base member initializer list
|
|
workspace(Creatable<Instance>::create<Workspace>(this)),
|
|
guiRoot(Creatable<Instance>::create<GuiRoot>()),
|
|
forceArrowCursor(true),
|
|
isGameLoaded(false),
|
|
areCoreScriptsLoaded(false),
|
|
game(NULL),
|
|
networkStatsWindowsOn(false),
|
|
#pragma warning(pop)
|
|
dataModelInitTime(Time::nowFast()),
|
|
savePlaceThrottle(&DFInt::SavePlacePerMinute),
|
|
vipServerId(""),
|
|
vipServerOwnerId(0),
|
|
renderGuisActive(DFFlag::AllowHideHudShortcutDefault),
|
|
canRequestUniverseInfo(false),
|
|
universeDataRequested(false),
|
|
forceR15(false)
|
|
{
|
|
if (TaskScheduler::singleton().isCyclicExecutive())
|
|
{
|
|
TaskScheduler::singleton().DataModel30fpsThrottle = DataModel::throttleAt30Fps;
|
|
}
|
|
|
|
|
|
count++;
|
|
lockParent();
|
|
|
|
guiBuilder.Initialize(this);
|
|
}
|
|
|
|
bool DataModel::getRemoteBuildMode()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void DataModel::setRemoteBuildMode(bool remoteBuildMode)
|
|
{
|
|
}
|
|
|
|
void DataModel::setScreenshotSEOInfo(std::string value)
|
|
{
|
|
screenshotSEOInfo = value;
|
|
}
|
|
|
|
void DataModel::setVideoSEOInfo(std::string value)
|
|
{
|
|
videoSEOInfo = value;
|
|
}
|
|
|
|
std::string DataModel::getScreenshotSEOInfo()
|
|
{
|
|
return screenshotSEOInfo;
|
|
}
|
|
|
|
// TODO: Refactor. this is gross. can we get away without it? Or put all other stuff here, too?
|
|
void DataModel::initializeContents(bool startHeartbeat)
|
|
{
|
|
RBXASSERT(!isInitialized);
|
|
workspace->setAndLockParent(this);
|
|
guiRoot->setAndLockParent(this);
|
|
|
|
setIsPersonalServer(false);
|
|
|
|
// Add basic services that are always there
|
|
ServiceProvider::create<NonReplicatedCSGDictionaryService>();
|
|
ServiceProvider::create<CSGDictionaryService>();
|
|
ServiceProvider::create<LogService>();
|
|
ServiceProvider::create<ContentProvider>();
|
|
ServiceProvider::create<ContentFilter>();
|
|
ServiceProvider::create<KeyframeSequenceProvider>();
|
|
ServiceProvider::create<GuiService>();
|
|
ServiceProvider::create<ChatService>();
|
|
ServiceProvider::create<MarketplaceService>();
|
|
ServiceProvider::create<PointsService>();
|
|
ServiceProvider::create<AdService>();
|
|
ServiceProvider::create<NotificationService>();
|
|
ServiceProvider::create<ReplicatedFirst>();
|
|
ServiceProvider::create<HttpRbxApiService>();
|
|
|
|
starterPlayerService = shared_from(ServiceProvider::create<StarterPlayerService>());
|
|
|
|
starterPackService = shared_from(ServiceProvider::create<StarterPackService>());
|
|
|
|
starterGuiService = shared_from(ServiceProvider::create<StarterGuiService>());
|
|
|
|
coreGuiService = shared_from(ServiceProvider::create<CoreGuiService>());
|
|
coreGuiService->createRobloxScreenGui();
|
|
|
|
if (TeleportService* ts = ServiceProvider::create<TeleportService>(this))
|
|
{
|
|
if (TeleportService::getCustomTeleportLoadingGui() && TeleportService::didTeleport())
|
|
{
|
|
if (RBX::ScreenGui* loadingGui = Instance::fastDynamicCast<RBX::ScreenGui>(TeleportService::getCustomTeleportLoadingGui().get()))
|
|
{
|
|
ts->setTempCustomTeleportLoadingGui(loadingGui->clone(EngineCreator));
|
|
ts->getTempCustomTeleportLoadingGui()->setParent(coreGuiService->getRobloxScreenGui().get());
|
|
ts->setCustomTeleportLoadingGui(loadingGui->clone(EngineCreator));
|
|
}
|
|
}
|
|
}
|
|
|
|
runService = shared_from(ServiceProvider::create<RunService>());
|
|
|
|
ServiceProvider::create<Soundscape::SoundService>();
|
|
|
|
workspace->setDefaultMouseCommand();
|
|
|
|
workspace->setVerbParent(this);
|
|
|
|
isPersonalServer = false;
|
|
|
|
if (startHeartbeat)
|
|
runService->start();
|
|
|
|
// No need to save the connection. The run service will be deleted before we are
|
|
runService->runTransitionSignal.connect(boost::bind(&DataModel::onRunTransition, this, _1));
|
|
|
|
// TODO: Do we really need to pre-create these services? It adds #include hell to this file.
|
|
// Most services should be created on-demand, even in a Lua startup script, but not here!
|
|
ServiceProvider::create<JointsService>();
|
|
ServiceProvider::create<CollectionService>();
|
|
ServiceProvider::create<PhysicsService>();
|
|
ServiceProvider::create<BadgeService>();
|
|
ServiceProvider::create<GeometryService>();
|
|
ServiceProvider::create<FriendService>();
|
|
ServiceProvider::create<RenderHooksService>();
|
|
ServiceProvider::create<InsertService>();
|
|
ServiceProvider::create<SocialService>();
|
|
ServiceProvider::create<GamePassService>();
|
|
ServiceProvider::create<DebrisService>();
|
|
ServiceProvider::create<ScriptInformationProvider>();
|
|
ServiceProvider::create<CookiesService>();
|
|
ServiceProvider::create<TeleportService>();
|
|
ServiceProvider::create<PersonalServerService>();
|
|
ServiceProvider::create<Network::Players>(); // We always need this, because it saves state
|
|
userInputService = shared_from(ServiceProvider::create<UserInputService>());
|
|
contextActionService = shared_from(ServiceProvider::create<ContextActionService>()); // store a ref since we use this in processInputObject
|
|
ServiceProvider::create<ScriptService>();
|
|
ServiceProvider::create<AssetService>();
|
|
|
|
}
|
|
|
|
void DataModel::loadCoreScripts(const std::string &altStarterScript)
|
|
{
|
|
if (!areCoreScriptsLoaded)
|
|
{
|
|
areCoreScriptsLoaded = true;
|
|
std::string baseUrl = ServiceProvider::create<ContentProvider>(this)->getBaseUrl();
|
|
|
|
if (!baseUrl.empty() && baseUrl[baseUrl.size() - 1] != '/')
|
|
baseUrl += '/';
|
|
|
|
StudioConfigurer sc;
|
|
sc.starterScript = altStarterScript;
|
|
sc.configure(Security::COM, this, format("{ \"BaseUrl\": \"%s\" }", baseUrl.c_str()));
|
|
}
|
|
}
|
|
|
|
|
|
void DataModel::startCoreScripts(bool buildInGameGui, const std::string &altStarterScript)
|
|
{
|
|
// Build Gui - all verbs should be set
|
|
guiBuilder.buildGui(this->workspace.get(), buildInGameGui);
|
|
loadCoreScripts(altStarterScript);
|
|
}
|
|
|
|
|
|
rbx::atomic<int> DataModel::count;
|
|
|
|
DataModel::~DataModel()
|
|
{
|
|
count--;
|
|
if(placeID != 0)
|
|
RbxDbgInfo::RemovePlace(placeID);
|
|
}
|
|
|
|
|
|
|
|
struct DataModel::LegacyLock::Implementation : boost::noncopyable
|
|
{
|
|
// To marshal the task to this thread we use a pair of locks (acquire and release)
|
|
// Since creating mutexes are expensive, we push spent Events into a pool
|
|
// for re-use
|
|
struct Events
|
|
{
|
|
CEvent acquiredLock;
|
|
CEvent releasedLock;
|
|
Events()
|
|
:acquiredLock(false)
|
|
,releasedLock(false)
|
|
{}
|
|
};
|
|
SAFE_STATIC(rbx::safe_queue< shared_ptr<Events> >, eventsPool)
|
|
|
|
// The job in this thread that has currently acquired a lock (if any)
|
|
// This thread specific pointer is used to re-entrancy checks
|
|
SAFE_STATIC(rbx::thread_specific_reference<GenericJob>, currentJob)
|
|
|
|
DataModel* const dataModel;
|
|
|
|
// The job being used for scheduling:
|
|
GenericJob* job;
|
|
// The previous job, if any, being used for scheduling:
|
|
GenericJob* previousJob;
|
|
// The mutexes used to marshal the task across threads:
|
|
shared_ptr<Events> events;
|
|
|
|
boost::scoped_ptr<DataModel::scoped_write_transfer> writeTransfer;
|
|
|
|
static void task(shared_ptr<Events> events)
|
|
{
|
|
RBXPROFILER_SCOPE("Jobs", "LegacyLock");
|
|
|
|
// Signal the thread that constructed the Implementation object to proceed
|
|
events->acquiredLock.Set();
|
|
|
|
// Wait for Implementation object to go out of scope (task complete)
|
|
events->releasedLock.Wait();
|
|
|
|
// Recycle the events pair
|
|
eventsPool().push(events);
|
|
}
|
|
|
|
Implementation(DataModel* dataModel, boost::shared_ptr<GenericJob> job)
|
|
:job(job.get())
|
|
,dataModel(dataModel)
|
|
{
|
|
RBXASSERT(job);
|
|
previousJob = currentJob().get();
|
|
// Re-entrancy test. No need to wait if we're recursively locked on this DataModel
|
|
if (this->job != previousJob)
|
|
{
|
|
FASTLOG2(FLog::LegacyLock, "LegacyLock::Begin, DataModel: (%p), type(%d)", dataModel, job->taskType);
|
|
// Set the current job for this thread
|
|
currentJob().reset(this->job);
|
|
|
|
// Get/create a pair of events for marshalling
|
|
if (!eventsPool().pop_if_present(events))
|
|
events.reset(new Events());
|
|
|
|
// Submit the proxy task for scheduling.
|
|
job->tasks.push(boost::bind(&Implementation::task, events));
|
|
TaskScheduler::singleton().reschedule(job);
|
|
|
|
// Wait for the task to signal acquiredLock
|
|
events->acquiredLock.Wait();
|
|
|
|
if (job->taskType == DataModelJob::Write)
|
|
writeTransfer.reset(new DataModel::scoped_write_transfer(dataModel));
|
|
|
|
FASTLOG2(FLog::LegacyLock, "LegacyLock::Acquired type(%d), job (%s), events (%p)", job->taskType, events.get());
|
|
}
|
|
else
|
|
{
|
|
if (job->taskType == DataModelJob::Write)
|
|
RBXASSERT(dataModel->currentThreadHasWriteLock());
|
|
|
|
FASTLOG2(FLog::LegacyLock, "LegacyLock::RecursivelyAcquired DataModel: (%p), type(%d)", dataModel, job->taskType);
|
|
}
|
|
}
|
|
|
|
~Implementation()
|
|
{
|
|
FASTLOG2(FLog::LegacyLock, "LegacyLock::~Implementation type(%d), events (%p)", job->taskType, events.get());
|
|
|
|
if (job != previousJob)
|
|
{
|
|
if (job->taskType == DataModelJob::Write)
|
|
writeTransfer.reset();
|
|
|
|
// The task has completed, so undo state
|
|
currentJob().reset(previousJob);
|
|
// Signal the proxy task that we're done
|
|
events->releasedLock.Set();
|
|
FASTLOG2(FLog::LegacyLock, "LegacyLock::Released DataModel: (%p), type(%d)", dataModel, job->taskType);
|
|
}
|
|
else
|
|
{
|
|
if (job->taskType == DataModelJob::Write)
|
|
RBXASSERT(dataModel->currentThreadHasWriteLock());
|
|
|
|
FASTLOG2(FLog::LegacyLock, "LegacyLock::RecursivelyReleased DataModel: (%p), type(%d)", dataModel, job->taskType);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
|
|
DataModel::LegacyLock::Impersonator::Impersonator(shared_ptr<DataModel> dataModel, DataModelJob::TaskType taskType)
|
|
:previousJob(Implementation::currentJob().get())
|
|
{
|
|
Implementation::currentJob().reset(dataModel->getGenericJob(taskType).get());
|
|
}
|
|
DataModel::LegacyLock::Impersonator::~Impersonator()
|
|
{
|
|
Implementation::currentJob().reset(previousJob);
|
|
}
|
|
|
|
DataModel::LegacyLock::LegacyLock(boost::shared_ptr<DataModel> dataModel, DataModelJob::TaskType taskType)
|
|
// TODO: What if an invalid DataModelJob::Function is passed in???
|
|
:implementation(dataModel ? new Implementation(dataModel.get(), dataModel->getGenericJob(taskType)) : 0)
|
|
{
|
|
//assert(GetCurrentThreadId() != mainThreadId);
|
|
}
|
|
|
|
DataModel::LegacyLock::LegacyLock(DataModel* dataModel, DataModelJob::TaskType taskType)
|
|
:implementation(dataModel ? new Implementation(dataModel, dataModel->getGenericJob(taskType)) : 0)
|
|
{
|
|
//assert(GetCurrentThreadId() != mainThreadId);
|
|
}
|
|
|
|
DataModel::LegacyLock::~LegacyLock()
|
|
{
|
|
}
|
|
|
|
shared_ptr<DataModel::GenericJob> DataModel::tryGetGenericJob(DataModelJob::TaskType taskType)
|
|
{
|
|
RBX::mutex::scoped_lock lock(genericJobsLock);
|
|
return genericJobs[taskType];
|
|
}
|
|
|
|
shared_ptr<DataModel::GenericJob> DataModel::getGenericJob(DataModelJob::TaskType taskType)
|
|
{
|
|
shared_ptr<DataModel::GenericJob> job = tryGetGenericJob(taskType);
|
|
if (!job)
|
|
{
|
|
// This can also happen if the job is requested after the data model is closed.
|
|
throw std::runtime_error("Can't submit requested task type");
|
|
}
|
|
return job;
|
|
}
|
|
|
|
void DataModel::submitTask(Task task, DataModelJob::TaskType taskType)
|
|
{
|
|
if (shared_ptr<GenericJob> job = tryGetGenericJob(taskType))
|
|
{
|
|
job->tasks.push(task);
|
|
TaskScheduler::singleton().reschedule(job);
|
|
}
|
|
}
|
|
|
|
void DataModel::raiseClose()
|
|
{
|
|
closingSignal();
|
|
closingLateSignal();
|
|
}
|
|
|
|
void DataModel::traverseDataModelReporting(shared_ptr<Instance> node)
|
|
{
|
|
std::string className = node->getClassNameStr();
|
|
if(dataModelReportingData.find(className) == dataModelReportingData.end()){
|
|
dataModelReportingData[className] = 0;
|
|
}
|
|
dataModelReportingData[className]++;
|
|
}
|
|
|
|
void DataModel::setServerSaveUrl(std::string url)
|
|
{
|
|
serverSaveUrl = url;
|
|
}
|
|
|
|
bool DataModel::canSaveLocal() const
|
|
{
|
|
#ifdef _WIN32
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void DataModel::saveToRoblox(boost::function<void(bool)> resumeFunction, boost::function<void(std::string)> errorFunction)
|
|
{
|
|
if(canSave(this)){
|
|
if (Visit* visit = RBX::ServiceProvider::find<Visit>(this))
|
|
{
|
|
std::string uploadUrl = visit->getUploadUrl();
|
|
if(uploadUrl.find("http") == 0){
|
|
//needs to start with http
|
|
internalSaveAsync(ContentId(uploadUrl), resumeFunction);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
resumeFunction(false);
|
|
}
|
|
|
|
void DataModel::save(ContentId contentId)
|
|
{
|
|
if(canSave(this)){
|
|
internalSave(contentId);
|
|
}
|
|
saveFinishedSignal();
|
|
}
|
|
|
|
|
|
void DataModel::HttpHelper(std::string* response, std::exception* exception, boost::function<void(std::string)> resumeFunction, boost::function<void(std::string)> errorFunction)
|
|
{
|
|
if(response){
|
|
resumeFunction(*response);
|
|
}
|
|
else{
|
|
errorFunction(exception->what());
|
|
}
|
|
}
|
|
|
|
void DataModel::doHttpGet(const std::string& url, boost::function<void(std::string)> resumeFunction, boost::function<void(std::string)> errorFunction)
|
|
{
|
|
RBX::Http(url).get(boost::bind(&HttpHelper, _1, _2, resumeFunction, errorFunction));
|
|
}
|
|
|
|
std::string DataModel::doHttpGet(const std::string& url)
|
|
{
|
|
std::string response;
|
|
RBX::Http(url).get(response);
|
|
return response;
|
|
}
|
|
|
|
std::string DataModel::doHttpPost(const std::string& url, const std::string& data, const std::string& contentType)
|
|
{
|
|
std::string response;
|
|
std::istringstream stream(data);
|
|
Http(url).post(stream, contentType, data.size() > 256, response);
|
|
return response;
|
|
}
|
|
|
|
void DataModel::doHttpPost(const std::string& url, const std::string& data, const std::string& contentType, boost::function<void(std::string)> resumeFunction, boost::function<void(std::string)> errorFunction)
|
|
{
|
|
Http(url).post(data, contentType, data.size() > 256,
|
|
boost::bind(&HttpHelper, _1, _2, resumeFunction, errorFunction));
|
|
}
|
|
|
|
static void httpResumeCallback(const std::string& response)
|
|
{
|
|
}
|
|
|
|
static void httpErrorCallback(const std::string& url, const std::string& error)
|
|
{
|
|
StandardOut::singleton()->printf(MESSAGE_ERROR, "%s: %s", url.c_str(), error.c_str());
|
|
}
|
|
|
|
std::string DataModel::httpGet(std::string url, bool synchronous)
|
|
{
|
|
RBXASSERT(isInitialized); // Show to David or Erik - threading issue
|
|
robloxScriptModifiedCheck(RBX::httpGetFunction.security);
|
|
if (url.size()>0)
|
|
{
|
|
if (synchronous)
|
|
{
|
|
return doHttpGet(url);
|
|
}
|
|
else
|
|
{
|
|
doHttpGet(url, httpResumeCallback, boost::bind(httpErrorCallback, url, _1));
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
void DataModel::httpGetAsync(std::string url, boost::function<void(std::string)> resumeFunction, boost::function<void(std::string)> errorFunction)
|
|
{
|
|
RBXASSERT(isInitialized); // Show to David or Erik - threading issue
|
|
if (url.size() == 0)
|
|
{
|
|
errorFunction("Empty URL");
|
|
return;
|
|
}
|
|
robloxScriptModifiedCheck(RBX::httpGetAsyncFunction.security);
|
|
doHttpGet(url, resumeFunction, errorFunction);
|
|
}
|
|
|
|
void DataModel::reportMeasurement(std::string id, std::string key1, std::string value1, std::string key2, std::string value2)
|
|
{
|
|
ReportStatistic(GetBaseURL(), id, key1, value1, key2, value2);
|
|
}
|
|
|
|
std::string DataModel::httpPost(std::string url, std::string data, bool synchronous, std::string optionalContentType)
|
|
{
|
|
RBXASSERT(isInitialized); // Show to David or Erik - threading issue
|
|
|
|
robloxScriptModifiedCheck(RBX::httpPostFunction.security);
|
|
if (synchronous)
|
|
{
|
|
return doHttpPost(url, data, optionalContentType);
|
|
}
|
|
else
|
|
{
|
|
doHttpPost(url, data, optionalContentType, httpResumeCallback, boost::bind(httpErrorCallback, url, _1));
|
|
return "";
|
|
}
|
|
}
|
|
|
|
void DataModel::httpPostAsync(std::string url, std::string data, std::string optionalContentType, boost::function<void(std::string)> resumeFunction, boost::function<void(std::string)> errorFunction)
|
|
{
|
|
RBXASSERT(isInitialized); // Show to David or Erik - threading issue
|
|
|
|
if (url.size() == 0)
|
|
{
|
|
errorFunction("Empty URL");
|
|
return;
|
|
}
|
|
|
|
robloxScriptModifiedCheck(RBX::httpPostAsyncFunction.security);
|
|
doHttpPost(url, data, optionalContentType, resumeFunction, errorFunction);
|
|
}
|
|
|
|
void DataModel::luaReportGoogleAnalytics(std::string category, std::string action, std::string label, int value)
|
|
{
|
|
std::string encodedCategory = Http::urlEncode(category);
|
|
std::string encodedAction = Http::urlEncode(action);
|
|
std::string encodedLabel = Http::urlEncode(label);
|
|
RobloxGoogleAnalytics::trackEvent(encodedCategory.c_str(), encodedAction.c_str(), encodedLabel.c_str(), value);
|
|
}
|
|
|
|
std::auto_ptr<std::istream> DataModel::loadAssetIdIntoStream(int assetID)
|
|
{
|
|
// construct the url
|
|
std::string parameters = "asset/?id=" + boost::lexical_cast<std::string>(assetID);
|
|
std::string url = ServiceProvider::create<ContentProvider>(this)->getBaseUrl() + parameters;
|
|
|
|
StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "DataModel loading from: %s", url.c_str());
|
|
|
|
return std::auto_ptr<std::istream>(ServiceProvider::create<ContentProvider>(this)->getContent( ContentId(url) ));
|
|
}
|
|
|
|
void DataModel::loadWorld(int assetID)
|
|
{
|
|
if(!Network::Players::backendProcessing(this))
|
|
{
|
|
StandardOut::singleton()->printf(MESSAGE_WARNING, "Game:LoadWorld should only be called from a server script");
|
|
return;
|
|
}
|
|
|
|
std::auto_ptr<std::istream> stream = loadAssetIdIntoStream(assetID);
|
|
|
|
if(workspace)
|
|
{
|
|
workspace->clearTerrain();
|
|
workspace->removeAllChildren();
|
|
}
|
|
|
|
if (Lighting* lighting = ServiceProvider::find<Lighting>(this))
|
|
lighting->removeAllChildren();
|
|
if (Soundscape::SoundService* service = ServiceProvider::find<Soundscape::SoundService>(this))
|
|
service->removeAllChildren();
|
|
if (ServerStorage* service = ServiceProvider::find<ServerStorage>(this))
|
|
service->removeAllChildren();
|
|
if (ReplicatedStorage* service = ServiceProvider::find<ReplicatedStorage>(this))
|
|
service->removeAllChildren();
|
|
if (RobloxReplicatedStorage* service = ServiceProvider::find<RobloxReplicatedStorage>(this))
|
|
service->removeAllChildren();
|
|
if (ReplicatedFirst* service = ServiceProvider::find<ReplicatedFirst>(this))
|
|
service->removeAllChildren();
|
|
|
|
Serializer serializer;
|
|
serializer.load(*stream, this);
|
|
LuaSourceContainer::blockingLoadLinkedScripts(create<ContentProvider>(), this);
|
|
|
|
processAfterLoad();
|
|
}
|
|
|
|
void DataModel::loadGame(int assetID)
|
|
{
|
|
if(!Network::Players::backendProcessing(this))
|
|
{
|
|
StandardOut::singleton()->printf(MESSAGE_WARNING, "Game:LoadWorld should only be called from a server script");
|
|
return;
|
|
}
|
|
|
|
std::auto_ptr<std::istream> stream = loadAssetIdIntoStream(assetID);
|
|
|
|
if (ServerScriptService* service = ServiceProvider::find<ServerScriptService>(this))
|
|
service->removeAllChildren();
|
|
if (starterGuiService)
|
|
starterGuiService->removeAllChildren();
|
|
if (starterPackService)
|
|
starterPackService->removeAllChildren();
|
|
if (starterPlayerService)
|
|
starterPlayerService->removeAllChildren();
|
|
|
|
Serializer serializer;
|
|
serializer.load(*stream, this);
|
|
LuaSourceContainer::blockingLoadLinkedScripts(create<ContentProvider>(), this);
|
|
}
|
|
|
|
void DataModel::loadContent(ContentId contentId)
|
|
{
|
|
RBXASSERT(isInitialized); // Show to David or Erik - threading issue
|
|
|
|
if (isContentLoaded)
|
|
{
|
|
Analytics::GoogleAnalytics::trackEventWithoutThrottling(GA_CATEGORY_ERROR, "loadContent re-entrant", contentId.getAssetId().c_str());
|
|
return;
|
|
}
|
|
|
|
StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "DataModel Loading %s", contentId.c_str());
|
|
|
|
G3D::RealTime t1 = G3D::System::time(); // time in seconds
|
|
std::auto_ptr<std::istream> stream(ServiceProvider::create<ContentProvider>(this)->getContent(contentId, "Place"));
|
|
G3D::RealTime t2 = G3D::System::time();
|
|
|
|
// post-load check on RCC. An exploit that can overwrite pointers on RCC could jump to the
|
|
// middle of this function.
|
|
if (isContentLoaded)
|
|
{
|
|
stream.release();
|
|
Analytics::GoogleAnalytics::trackEventWithoutThrottling(GA_CATEGORY_ERROR, "loadContent re-entrant post", contentId.getAssetId().c_str());
|
|
return;
|
|
}
|
|
|
|
Serializer serializer;
|
|
serializer.load(*stream, this);
|
|
LuaSourceContainer::blockingLoadLinkedScripts(create<ContentProvider>(), this);
|
|
|
|
G3D::RealTime t3 = G3D::System::time();
|
|
processAfterLoad();
|
|
G3D::RealTime t4 = G3D::System::time();
|
|
|
|
workspace->setStatsSyncHttpGetTime(t2 - t1);
|
|
workspace->setStatsXMLLoadTime(t3 - t2);
|
|
workspace->setStatsJoinAllTime(t4 - t3);
|
|
|
|
workspace->createTerrain();
|
|
workspaceLoadedSignal();
|
|
|
|
isContentLoaded = true; // there should be a better way to do this.
|
|
|
|
}
|
|
|
|
void DataModel::processAfterLoad()
|
|
{
|
|
workspace->joinAllHack();
|
|
|
|
if (DFFlag::MaterialPropertiesEnabled)
|
|
{
|
|
// In case we have decided to make New Physical Properties the default,
|
|
// we want to migrate parts by default.
|
|
if (workspace->getUsingNewPhysicalProperties())
|
|
{
|
|
RBX::PartInstance::convertToNewPhysicalPropRecursive(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(RBX_STUDIO_BUILD) || defined(RBX_RCC_SECURITY) || defined(RBX_TEST_BUILD)
|
|
shared_ptr<const Instances> DataModel::fetchAsset(ContentId contentId)
|
|
{
|
|
RBXASSERT(isInitialized); // If hit show to David or Erik - threading issue
|
|
|
|
ContentProvider* cp = create<ContentProvider>();
|
|
std::auto_ptr<std::istream> stream(cp->getContent(contentId));
|
|
|
|
// Return all the new items in a table
|
|
shared_ptr<Instances> result(new Instances());
|
|
|
|
// Read the items
|
|
Serializer().loadInstances(*stream, *result);
|
|
LuaSourceContainer::blockingLoadLinkedScriptsForInstances(cp, *result);
|
|
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
void DataModel::onChildAdded(Instance* child)
|
|
{
|
|
Super::onChildAdded(child);
|
|
|
|
if (Network::Players::isNetworkClient(child))
|
|
{
|
|
// TODO: Hack
|
|
// For now, this hook sets the mouse command to the NULL command, regardless of whether there is a player/character or not
|
|
// This prevents the arrow tool from coming up on a multiplayer load
|
|
workspace->setNullMouseCommand();
|
|
}
|
|
}
|
|
|
|
|
|
bool DataModel::askAddChild(const Instance* instance) const
|
|
{
|
|
return dynamic_cast<const Service*>(instance)!=NULL;
|
|
}
|
|
|
|
|
|
double DataModel::getGameTime() const
|
|
{
|
|
RBXASSERT(runService);
|
|
return runService->gameTime();
|
|
}
|
|
|
|
|
|
|
|
double DataModel::getSmoothFps() const
|
|
{
|
|
RBXASSERT(runService);
|
|
return runService->smoothFps();
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Rendering
|
|
//
|
|
|
|
void DataModel::computeGuiInset(Adorn* adorn)
|
|
{
|
|
|
|
Vector4 guiInset;
|
|
if (RBX::GuiService* guiService = RBX::ServiceProvider::find<RBX::GuiService>(this))
|
|
{
|
|
guiInset = guiService->getGlobalGuiInset();
|
|
}
|
|
else
|
|
{
|
|
guiInset = Vector4(0, 0, 0, 0);
|
|
}
|
|
|
|
adorn->setUserGuiInset(guiInset);
|
|
}
|
|
|
|
void DataModel::renderPlayerGui(Adorn* adorn)
|
|
{
|
|
RBX::Network::Player* player = RBX::Network::Players::findLocalPlayer(this);
|
|
if ( !Network::Players::isCloudEdit(this) && player )
|
|
{
|
|
for (size_t i = 0; i < player->numChildren(); i++)
|
|
{
|
|
Instance *child = player->getChild(i);
|
|
if (PlayerGui* playergui = Instance::fastDynamicCast<PlayerGui>(child))
|
|
playergui->render2d(adorn);
|
|
}
|
|
}
|
|
else
|
|
starterGuiService->render2d(adorn);
|
|
}
|
|
|
|
void DataModel::renderGuiRoot(Adorn* adorn)
|
|
{
|
|
guiBuilder.updateGui();
|
|
guiRoot->render2d(adorn);
|
|
}
|
|
|
|
std::string DataModel::getUpdatedMessageBoxText()
|
|
{
|
|
std::string printMessage = "";
|
|
if (uiMessage == "[[[progress]]]")
|
|
{
|
|
printMessage = "Loading... Instances: "
|
|
+ getMetric("numInstances")
|
|
+ " Bricks: "
|
|
+ getMetric("numPrimitives")
|
|
+ " Connectors: "
|
|
+ getMetric("numJoints");
|
|
|
|
MegaClusterInstance* terrain = Instance::fastDynamicCast<MegaClusterInstance>(
|
|
workspace->getTerrain());
|
|
unsigned int cellCount = 0;
|
|
if (terrain) {
|
|
cellCount = terrain->getNonEmptyCellCount();
|
|
}
|
|
if (cellCount > 0) {
|
|
std::string result(15, '\0');
|
|
snprintf(&result[0], 15, "%.3f mil", cellCount / 1000000.0f);
|
|
|
|
printMessage += " Voxels: " + result;
|
|
}
|
|
}
|
|
else if(uiMessage != "[[[progress]]]")
|
|
printMessage = uiMessage;
|
|
|
|
return printMessage;
|
|
}
|
|
|
|
bool DataModel::canRenderMouse()
|
|
{
|
|
if (userInputService->getLastInputType() == InputObject::TYPE_TOUCH)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (userInputService->getOverrideMouseIconBehavior() == UserInputService::OVERRIDE_BEHAVIOR_FORCEHIDE)
|
|
{
|
|
return false;
|
|
}
|
|
else if (userInputService->getOverrideMouseIconBehavior() == UserInputService::OVERRIDE_BEHAVIOR_FORCESHOW)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if(!userInputService->getMouseIconEnabled())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool playerExists = (RBX::Network::Players::findLocalPlayer(this) != NULL) && !Network::Players::isCloudEdit(this);
|
|
|
|
if ( UserInputService::isTenFootInterface() )
|
|
{
|
|
return userInputService->getMouseIconEnabled();
|
|
}
|
|
|
|
return (!RBX::MouseCommand::isAdvArrowToolEnabled() || playerExists) && userInputService->getMouseEnabled();
|
|
}
|
|
|
|
void DataModel::renderPass2d(Adorn* adorn, IMetric* graphicsMetric)
|
|
{
|
|
RBXPROFILER_SCOPE("Render", "Pass2d");
|
|
|
|
RBXASSERT(isInitialized); // Show to David or Erik - threading issue
|
|
|
|
if (!isInitialized)
|
|
return;
|
|
if (!adorn)
|
|
return;
|
|
if (!graphicsMetric)
|
|
return;
|
|
|
|
tempMetric = graphicsMetric;
|
|
|
|
//compute header/footer to restrict the Gui (used for MFC chat, todo:remove this when we have lua chat)
|
|
computeGuiInset(adorn);
|
|
|
|
// 1. Render 2D world items (names, cursors, ScreenGuis, user items in workspace, etc.)
|
|
workspace->render2d(adorn);
|
|
|
|
if (!DFFlag::AllowHideHudShortcut || renderGuisActive)
|
|
{
|
|
// 2. Render the User created gui - either inside the PlayerGui, or the StarterGui
|
|
renderPlayerGui(adorn);
|
|
|
|
// 3. Render the ROBLOX gui elements
|
|
coreGuiService->render2d(adorn);
|
|
|
|
// 4. Render old school guiRoot (don't add anything to this, only debug stats are shown here: Shift-F1 and Shift-F2)
|
|
renderGuiRoot(adorn);
|
|
|
|
// 5. Finally, render the mouse
|
|
renderMouse(adorn);
|
|
}
|
|
|
|
tempMetric = NULL; // be safe, never use again unless set
|
|
RBXASSERT(isInitialized); // Show to David or Erik - threading issue
|
|
}
|
|
|
|
ContentId DataModel::getRenderMouseCursor()
|
|
{
|
|
if (forceArrowCursor)
|
|
{
|
|
if (!MouseCommand::isAdvArrowToolEnabled() ||
|
|
(RBX::Network::Players::findLocalPlayer(this) || Network::Players::serverIsPresent(this)))
|
|
{
|
|
return userInputService->getDefaultMouseCursor(true);
|
|
}
|
|
else
|
|
{
|
|
return userInputService->getDefaultMouseCursor(true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ContentId workspaceCursor = workspace->getCursor();
|
|
if (workspaceCursor.isNull())
|
|
{
|
|
return userInputService->getDefaultMouseCursor(true);
|
|
}
|
|
return workspaceCursor;
|
|
}
|
|
}
|
|
|
|
void DataModel::renderMouse(Adorn* adorn)
|
|
{
|
|
if (Profiler::isCapturingMouseInput())
|
|
return;
|
|
|
|
ControllerService* service = ServiceProvider::create<ControllerService>(this);
|
|
if(!service)
|
|
return;
|
|
|
|
if (UserInputService::IsUsingNewKeyboardEvents())
|
|
{
|
|
// don't need userinputbase for SDL
|
|
}
|
|
else
|
|
{
|
|
#ifndef RBX_PLATFORM_UWP
|
|
UserInputBase* hardwareDevice = service->getHardwareDevice();
|
|
if (hardwareDevice)
|
|
{
|
|
hardwareDevice->setCursorId(adorn, getRenderMouseCursor());
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (!canRenderMouse())
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool waiting = false;
|
|
|
|
// lazy connect to signal
|
|
if (!unbindResourceSignal.connected())
|
|
{
|
|
unbindResourceSignal = adorn->getUnbindResourcesSignal().connect(boost::bind(&DataModel::onUnbindResourceSignal, this));
|
|
}
|
|
|
|
renderCursor = adorn->createTextureProxy(getRenderMouseCursor(), waiting, false, "Mouse Cursor");
|
|
if (!renderCursor && userInputService)
|
|
{
|
|
renderCursor = adorn->createTextureProxy(userInputService->getDefaultMouseCursor(false), waiting);
|
|
}
|
|
|
|
if (renderCursor && userInputService->getCurrentMousePosition())
|
|
{
|
|
Rect2D rect = adorn->getTextureSize(renderCursor);
|
|
|
|
// Center the texture on the cursor position
|
|
rect = rect - rect.center() + userInputService->getCurrentMousePosition()->get2DPosition();
|
|
|
|
rect = Rect2D::xyxy( Math::roundVector2(rect.x0y0()), Math::roundVector2(rect.x1y1()) );
|
|
|
|
adorn->setTexture(0, renderCursor);
|
|
adorn->rect2d(rect, G3D::Color4(1,1,1,1));
|
|
adorn->setTexture(0, TextureProxyBaseRef());
|
|
}
|
|
}
|
|
|
|
void DataModel::onUnbindResourceSignal()
|
|
{
|
|
renderCursor.reset();
|
|
unbindResourceSignal.disconnect();
|
|
}
|
|
|
|
void DataModel::renderPass3dAdorn(Adorn* adorn)
|
|
{
|
|
RBXPROFILER_SCOPE("Render", "Pass3dAdorn");
|
|
|
|
RBXASSERT(isInitialized); // Show to David or Erik - threading issue
|
|
if (!isInitialized) {
|
|
return;
|
|
}
|
|
|
|
// a couple of local variables for the ctrl-F1 hud
|
|
drawId++;
|
|
|
|
//
|
|
if (!adorn) {
|
|
RBXASSERT(0);
|
|
return;
|
|
}
|
|
|
|
std::vector<AdornableDepth> sortedAdorn;
|
|
workspace->render3dAdorn(adorn);
|
|
workspace->append3dSortedAdorn(sortedAdorn);
|
|
|
|
RBX::Network::Players* players = ServiceProvider::create<RBX::Network::Players>(this);
|
|
if (RBX::Network::Player* player = players->getLocalPlayer()) {
|
|
if (PlayerGui* playerGui = player->findFirstChildOfType<PlayerGui>()) {
|
|
playerGui->render3dAdorn(adorn);
|
|
playerGui->append3dSortedAdorn(sortedAdorn, workspace->getConstCamera());
|
|
}
|
|
}
|
|
else{
|
|
starterGuiService->render3dAdorn(adorn);
|
|
starterGuiService->append3dSortedAdorn(sortedAdorn, workspace->getConstCamera());
|
|
}
|
|
|
|
coreGuiService->render3dAdorn(adorn);
|
|
coreGuiService->append3dSortedAdorn(sortedAdorn, workspace->getConstCamera());
|
|
|
|
std::sort(sortedAdorn.begin(), sortedAdorn.end());
|
|
|
|
FASTLOG1(FLog::AdornRenderStats, "Rendering 3D Sorted Adorn Items, %u items", sortedAdorn.size());
|
|
|
|
for (auto& ad: sortedAdorn)
|
|
ad.adornable->render3dSortedAdorn(adorn);
|
|
|
|
if (!forceArrowCursor) {
|
|
workspace->getCurrentMouseCommand()->render3dAdorn(adorn);
|
|
}
|
|
|
|
if (Workspace::showEPhysicsRegions) {
|
|
ServiceProvider::create<RBX::Network::Players>(this)->renderDPhysicsRegions(adorn);
|
|
}
|
|
|
|
if (Workspace::showStreamedRegions)
|
|
{
|
|
adorn->setObjectToWorldMatrix(CoordinateFrame());
|
|
if (RBX::Network::Player* player = players->getLocalPlayer())
|
|
player->renderStreamedRegion(adorn);
|
|
}
|
|
|
|
if (Workspace::showPartMovementPath)
|
|
{
|
|
adorn->setObjectToWorldMatrix(CoordinateFrame());
|
|
if (RBX::Network::Player* player = players->getLocalPlayer())
|
|
player->renderPartMovementPath(adorn);
|
|
}
|
|
|
|
RBXASSERT(isInitialized); // Show to David or Erik - threading issue
|
|
}
|
|
|
|
//Updates non-reflected state for camera and whatnot
|
|
void DataModel::renderStep(float timeIntervalSeconds)
|
|
{
|
|
RBXPROFILER_SCOPE("Render", "Step");
|
|
|
|
if (runService)
|
|
{
|
|
runService->earlyRenderSignal();
|
|
}
|
|
|
|
RBXASSERT(workspace);
|
|
if (FFlag::RelativisticCameraFixEnable && workspace)
|
|
{
|
|
if (Camera* camera = workspace->getCamera())
|
|
{
|
|
// Relativistic Bug (DE9363):
|
|
//
|
|
// Camera was being updated after the UI processing occurred, however the SurfaceGUI processing (which is part of the UI pipeline) required
|
|
// updated camera to perform raycasts. This resulted in a bug where the raycast would use the camera from previous frame - not good if the
|
|
// camera was attached to a moving object with SurfaceGUIs on it.
|
|
//
|
|
// The correct way would be to split out the UI processing that modifies the camera (e.g. movement, panning) into a separate call and do that before
|
|
// the camera update, and perform the rest afterwards.
|
|
//
|
|
// So this ugly hack will hang along for some indeterminate amount of time... Or perhaps for eternity.
|
|
|
|
camera->step(timeIntervalSeconds);
|
|
userInputService->onRenderStep();
|
|
camera->step(0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
userInputService->onRenderStep();
|
|
}
|
|
|
|
if (runService)
|
|
{
|
|
runService->renderStepped(timeIntervalSeconds, false);
|
|
}
|
|
|
|
// Need to step subject update after lua controls
|
|
if (Camera* camera = workspace->getCamera())
|
|
{
|
|
camera->stepSubject();
|
|
}
|
|
}
|
|
|
|
|
|
float DataModel::physicsStep(float timeInterval, double dt, double dutyDt, int numThreads)
|
|
{
|
|
if (!isInitialized) {
|
|
RBXASSERT(0);
|
|
return 0.0f; // 12/05/07 - possibly seeing this being called during destruction in other thread?
|
|
}
|
|
|
|
// On Mac there is nothing equivalent of _CrtCheckMemory to validate the state of the heap
|
|
// Instead use this on XCode http://developer.apple.com/library/ios/#documentation/Performance/Conceptual/ManagingMemory/Articles/MallocDebug.html
|
|
#ifdef _WIN32
|
|
RBXASSERT_IF_VALIDATING(_CrtCheckMemory());
|
|
#endif
|
|
bool isCyclicExecutive = RBX::TaskScheduler::singleton().isCyclicExecutive();
|
|
float dtCyclicExecutive = 0.0f;
|
|
if( isCyclicExecutive )
|
|
{
|
|
int nSteps = workspace->updatePhysicsStepsRequiredForCyclicExecutive(dt);
|
|
if( nSteps == 0 && DataModel::throttleAt30Fps)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
dtCyclicExecutive = (float)nSteps * Constants::worldDt();
|
|
}
|
|
|
|
// TaskSchedulerCyclicExecutive: This should simply be triggered after every
|
|
// 1/30s. Not changing it just yet.
|
|
|
|
bool longStep = physicsStepID % 2 == 0;
|
|
|
|
Profiling::Mark mark(*workspace->profileDataModelStep, true, true);
|
|
|
|
float timeIntervalUsed = 0.0f;
|
|
|
|
// Physics Step
|
|
RBX::Network::GameMode gameMode = Network::Players::getGameMode(this);
|
|
if (updatePhysicsInstructions(gameMode))
|
|
{
|
|
if ( (gameMode == Network::DPHYS_CLIENT || gameMode == Network::CLIENT || gameMode == Network::VISIT_SOLO ) && !isCyclicExecutive )
|
|
{
|
|
runService->gameStepped( Constants::uiDt(), longStep);
|
|
}
|
|
else if (longStep && !isCyclicExecutive)
|
|
{
|
|
runService->gameStepped( Constants::longUiStepDt(), true);
|
|
}
|
|
else if (isCyclicExecutive)
|
|
{
|
|
runService->gameStepped(dtCyclicExecutive, true);
|
|
}
|
|
|
|
{
|
|
RBX::Profiling::Mark mark2(*workspace->profileWorkspaceAssemble, true);
|
|
workspace->assemble(); // must do assembly here, because "handle fallen parts" may disassemble. Also, runService->gameStepped may disassemble
|
|
}
|
|
{
|
|
Profiling::Mark mark2(*workspace->profileWorkspaceStep, true, true);
|
|
|
|
Network::Player* dPhysPlayer = (gameMode == Network::DPHYS_CLIENT) ? Network::Players::findLocalPlayer(this) : NULL;
|
|
|
|
FASTLOG3F(FLog::CyclicExecutiveThrottling, "DataModel::PhysicsStep dt: %f, dtCyclicExecutive: %f, dutyDt: %f", dt, dtCyclicExecutive, dutyDt);
|
|
if (isCyclicExecutive)
|
|
{
|
|
physicsInstructions.setCyclicThrottles(dPhysPlayer, workspace.get(), dtCyclicExecutive, dt, dutyDt);
|
|
}
|
|
else
|
|
{
|
|
physicsInstructions.setThrottles(dPhysPlayer, workspace.get(), dt, dutyDt);
|
|
}
|
|
timeIntervalUsed = workspace->physicsStep(longStep, timeInterval, numThreads);
|
|
FASTLOG1F(FLog::CyclicExecutiveTiming, "DataModel::physicsStepped: %4.7f", timeIntervalUsed);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Assemble always
|
|
RBX::Profiling::Mark mark2(*workspace->profileWorkspaceAssemble, true);
|
|
workspace->assemble();
|
|
}
|
|
|
|
if (gameMode == RBX::Network::DPHYS_GAME_SERVER && (physicsStepID % 4) == 0)
|
|
{
|
|
// only server records the movement history, once every 3 physics steps
|
|
workspace->updateHistory();
|
|
}
|
|
|
|
++physicsStepID;
|
|
|
|
// On Mac there is nothing equivalent of _CrtCheckMemory to validate the state of the heap
|
|
// Instead use this on XCode http://developer.apple.com/library/ios/#documentation/Performance/Conceptual/ManagingMemory/Articles/MallocDebug.html
|
|
#ifdef _WIN32
|
|
RBXASSERT_IF_VALIDATING(_CrtCheckMemory());
|
|
#endif
|
|
|
|
RBXASSERT(isInitialized); // Show to David or Erik - threading issue
|
|
|
|
return timeIntervalUsed;
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: make these objects
|
|
bool DataModel::updatePhysicsInstructions(Network::GameMode gameMode)
|
|
{
|
|
SimSendFilter& simSendFilter = workspace->getWorld()->getSimSendFilter();
|
|
simSendFilter.networkAddress = RBX::Network::Players::findLocalSimulatorAddress(this);
|
|
physicsInstructions.requestedDutyPercent = 0.0;
|
|
physicsInstructions.bandwidthExceeded = false;
|
|
|
|
switch (gameMode)
|
|
{
|
|
case Network::VISIT_SOLO:
|
|
case Network::EDIT:
|
|
{
|
|
RBXASSERT(simSendFilter.networkAddress == Network::NetworkOwner::Unassigned());
|
|
simSendFilter.mode = SimSendFilter::EditVisit;
|
|
physicsInstructions.requestedDutyPercent = PhysicsInstructions::visitSoloDutyPercent();
|
|
return (runService->getRunState() == RS_RUNNING);
|
|
}
|
|
case Network::GAME_SERVER:
|
|
{
|
|
RBXASSERT(simSendFilter.networkAddress == Network::NetworkOwner::Unassigned());
|
|
simSendFilter.mode = SimSendFilter::Server;
|
|
physicsInstructions.requestedDutyPercent = PhysicsInstructions::regularServerDutyPercent();
|
|
return (runService->getRunState() == RS_RUNNING);
|
|
}
|
|
case Network::DPHYS_GAME_SERVER:
|
|
{
|
|
RBXASSERT(simSendFilter.networkAddress == Network::NetworkOwner::Server());
|
|
simSendFilter.mode = SimSendFilter::dPhysServer;
|
|
simSendFilter.region.clearEmpty();
|
|
physicsInstructions.requestedDutyPercent = PhysicsInstructions::dPhysicsServerDutyPercent();
|
|
return (runService->getRunState() == RS_RUNNING);
|
|
}
|
|
case Network::CLIENT:
|
|
case Network::WATCH_ONLINE:
|
|
{
|
|
RBXASSERT(simSendFilter.networkAddress == Network::NetworkOwner::Unassigned());
|
|
simSendFilter.mode = SimSendFilter::Client;
|
|
physicsInstructions.requestedDutyPercent = PhysicsInstructions::zeroDutyPercent();
|
|
return false;
|
|
}
|
|
case Network::DPHYS_CLIENT:
|
|
{
|
|
RBXASSERT(Network::Players::clientIsPresent(this, true));
|
|
simSendFilter.mode = SimSendFilter::dPhysClient;
|
|
ServiceProvider::create<RBX::Network::Players>(this)->buildClientRegion(simSendFilter.region);
|
|
physicsInstructions.requestedDutyPercent = PhysicsInstructions::dPhysicsClientEThrottleDutyPercent();
|
|
|
|
physicsInstructions.bandwidthExceeded = Network::Player::physicsOutBandwidthExceeded(this);
|
|
physicsInstructions.networkBufferHealth = Network::Player::getNetworkBufferHealth(this);
|
|
return true;
|
|
}
|
|
default:
|
|
{
|
|
RBXASSERT(0);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DataModel::checkFetchExperimentalFeatures()
|
|
{
|
|
if(getPlaceID() == 0)
|
|
{
|
|
FASTLOG(FLog::CloseDataModel, "Place Id is zero, skipping fetch");
|
|
return;
|
|
}
|
|
|
|
if(checkedExperimentalFeatures)
|
|
return;
|
|
|
|
ContentProvider* cp = find<ContentProvider>();
|
|
if(!cp)
|
|
{
|
|
RBXASSERT(false);
|
|
return;
|
|
}
|
|
|
|
std::string response;
|
|
|
|
if (RBX::HttpRbxApiService* apiService = RBX::ServiceProvider::find<RBX::HttpRbxApiService>(this))
|
|
{
|
|
apiService->get(RBX::format("game/GetAllowedExperimentalFeatures?placeId=%i", getPlaceID()), true, PRIORITY_EXTREME, response);
|
|
}
|
|
|
|
std::stringstream rawStream(response);
|
|
shared_ptr<const Reflection::ValueTable> result;
|
|
if(!WebParser::parseJSONTable(response, result))
|
|
{
|
|
FASTLOG(FLog::CloseDataModel, "Can't parse JSON");
|
|
RBXASSERT(false);
|
|
checkedExperimentalFeatures = true;
|
|
return;
|
|
}
|
|
|
|
checkedExperimentalFeatures = true;
|
|
}
|
|
|
|
void DataModel::onRunTransition(RunTransition event)
|
|
{
|
|
RBXASSERT(isInitialized); // Show to David or Erik - threading issue
|
|
if (!isInitialized) {
|
|
return;
|
|
}
|
|
|
|
switch (event.newState)
|
|
{
|
|
case RS_STOPPED:
|
|
// only compiles in if we have a timer
|
|
|
|
if (event.oldState == RS_RUNNING) {
|
|
workspace->reset();
|
|
}
|
|
break;
|
|
|
|
case RS_RUNNING:
|
|
workspace->start();
|
|
break;
|
|
|
|
case RS_PAUSED:
|
|
workspace->stop();
|
|
break;
|
|
}
|
|
RBXASSERT(isInitialized); // Show to David or Erik - threading issue
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
// inquiry....
|
|
|
|
static void notificationCallbackError(std::string error)
|
|
{
|
|
}
|
|
|
|
static void notificationCallbackSuccess(DataModel* dm)
|
|
{
|
|
dm->setRenderGuisActive(false);
|
|
}
|
|
|
|
GuiResponse DataModel::processAccelerators(const shared_ptr<InputObject>& event) // true if used the event
|
|
{
|
|
Verb* verb = NULL;
|
|
bool preventSinking = false;
|
|
|
|
if (networkStatsWindowsOn)
|
|
{
|
|
switch (event->getUserInputType())
|
|
{
|
|
case InputObject::TYPE_KEYBOARD:
|
|
{
|
|
// we only want key down events
|
|
if(!event->isKeyDownEvent())
|
|
break;
|
|
|
|
switch (event->getKeyCode())
|
|
{
|
|
case SDLK_1:
|
|
if (event->isKeyPressedWithShiftEvent(event->getKeyCode()))
|
|
guiBuilder.nextNetworkStats();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (event->getUserInputType())
|
|
{
|
|
case InputObject::TYPE_KEYBOARD:
|
|
{
|
|
RBXASSERT(event->isKeyDownEvent() || event->isKeyUpEvent());
|
|
|
|
if(event->isKeyUpEvent())
|
|
{
|
|
switch(event->getKeyCode())
|
|
{
|
|
case SDLK_LALT:
|
|
case SDLK_RALT:
|
|
RBX::GameBasicSettings::singleton().setFreeLook(false);
|
|
break;
|
|
default:
|
|
RBX::GameBasicSettings::singleton().setFreeLook(false);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// if not keydown at this point, bail out
|
|
if(!event->isKeyDownEvent())
|
|
break;
|
|
|
|
{
|
|
if (ServiceProvider::find<GuiService>()->processKeyDown(event))
|
|
{
|
|
return GuiResponse::sunk();
|
|
}
|
|
else
|
|
{
|
|
bool isCustomCameraType = false;
|
|
if (const Camera* camera = workspace->getConstCamera())
|
|
{
|
|
isCustomCameraType = (camera->getCameraType() == Camera::CUSTOM_CAMERA);
|
|
}
|
|
bool shouldDoCameraVerbs = !isCustomCameraType;
|
|
if (const Camera* camera = workspace->getConstCamera())
|
|
{
|
|
if (FFlag::UserAllCamerasInLua && camera->hasClientPlayer())
|
|
{
|
|
shouldDoCameraVerbs = false;
|
|
}
|
|
}
|
|
|
|
switch (event->getKeyCode())
|
|
{
|
|
case SDLK_i:
|
|
if (shouldDoCameraVerbs)
|
|
{
|
|
verb = workspace->getWhitelistVerb("Camera", "Zoom", "In");
|
|
}
|
|
break;
|
|
case SDLK_o:
|
|
if (shouldDoCameraVerbs)
|
|
{
|
|
verb = workspace->getWhitelistVerb("Camera", "Zoom", "Out");
|
|
}
|
|
break;
|
|
case SDLK_COMMA:
|
|
// Camera panning is hard-coded to be always enabled, so we just do a quick check before we get a camera panning verb, so this doesn't break the Scriptable Camera
|
|
if (shouldDoCameraVerbs && workspace->getConstCamera()->getCameraType() != Camera::LOCKED_CAMERA)
|
|
{
|
|
verb = workspace->getWhitelistVerb("Camera", "Pan", "Left");
|
|
}
|
|
break;
|
|
case SDLK_PERIOD:
|
|
if (shouldDoCameraVerbs && workspace->getConstCamera()->getCameraType() != Camera::LOCKED_CAMERA)
|
|
{
|
|
verb = workspace->getWhitelistVerb("Camera", "Pan", "Right");
|
|
}
|
|
break;
|
|
case SDLK_PAGEUP:
|
|
if (shouldDoCameraVerbs)
|
|
{
|
|
verb = workspace->getWhitelistVerb("Camera", "Tilt", "Up");
|
|
}
|
|
break;
|
|
case SDLK_PAGEDOWN:
|
|
if (shouldDoCameraVerbs)
|
|
{
|
|
verb = workspace->getWhitelistVerb("Camera", "Tilt", "Down");
|
|
}
|
|
break;
|
|
case SDLK_BACKSPACE:
|
|
{
|
|
if (RBX::Network::Players* players = ServiceProvider::create<RBX::Network::Players>(this))
|
|
{
|
|
if (RBX::Network::Player* player = players->getLocalPlayer())
|
|
{
|
|
Tool::dropAll(player);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SDLK_EQUALS:
|
|
{
|
|
if (ModelInstance* character = Network::Players::findLocalCharacter(this))
|
|
{
|
|
Accoutrement::dropAll(character);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/**************************************/
|
|
|
|
case SDLK_F1:
|
|
if ( !RBX::GameBasicSettings::singleton().inStudioMode() )
|
|
{
|
|
if (event->mod==0)
|
|
verb = getWhitelistVerb("H", "el", "p");
|
|
else
|
|
verb = getWhitelistVerb("S", "ta", "ts");
|
|
}
|
|
break;
|
|
|
|
case SDLK_F2:
|
|
if ( !RBX::GameBasicSettings::singleton().inStudioMode() && event->isKeyPressedWithShiftEvent(event->getKeyCode()) )
|
|
verb = getWhitelistVerb("", "Render", "Stats");
|
|
/*else
|
|
verb = getWhitelistVerb("TogglePlayMode");*/
|
|
// TODO: turn on above statement when f2 studio hack no longer is an issue
|
|
|
|
break;
|
|
case SDLK_F3:
|
|
if ( !RBX::GameBasicSettings::singleton().inStudioMode() && event->isKeyPressedWithShiftEvent(event->getKeyCode()) )
|
|
verb = getWhitelistVerb("Network", "", "Stats");
|
|
break;
|
|
|
|
case SDLK_F4:
|
|
if ( !RBX::GameBasicSettings::singleton().inStudioMode() && event->isKeyPressedWithShiftEvent(event->getKeyCode()) )
|
|
verb = getWhitelistVerb("Physics", "Stats", "");
|
|
break;
|
|
|
|
case SDLK_F5: // TODO: use for run command in studio
|
|
if ( !RBX::GameBasicSettings::singleton().inStudioMode() && event->isKeyPressedWithShiftEvent(event->getKeyCode()) )
|
|
verb = getWhitelistVerb("Summary", "" , "Stats");
|
|
break;
|
|
|
|
case SDLK_F6:
|
|
if ( !RBX::GameBasicSettings::singleton().inStudioMode() && event->isKeyPressedWithShiftEvent(event->getKeyCode()) )
|
|
verb = getWhitelistVerb("Custom", "Stats", "");
|
|
break;
|
|
case SDLK_F7:
|
|
if (DFFlag::AllowHideHudShortcut && !RBX::GameBasicSettings::singleton().getUsedHideHudShortcut())
|
|
{
|
|
if (RBX::GuiService* guiService = RBX::ServiceProvider::find<RBX::GuiService>(this))
|
|
{
|
|
if (!guiService->notificationCallback.empty())
|
|
{
|
|
RBX::GameBasicSettings::singleton().setUsedHideHudShortcut(true);
|
|
guiService->notificationCallback("HUD Hidden", "Press F7 again when you want to unhide all GUIs", boost::bind(notificationCallbackSuccess, this), boost::bind(notificationCallbackError, _1));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
renderGuisActive = !renderGuisActive;
|
|
}
|
|
break;
|
|
case SDLK_F8:
|
|
if ( event->isKeyPressedWithCtrlEvent(event->getKeyCode()) )
|
|
{
|
|
if( getWorkspace()->getWorld() && getWorkspace()->getWorld()->getKernel() )
|
|
{
|
|
getWorkspace()->getWorld()->getKernel()->dumpLog( !event->isKeyPressedWithShiftEvent(event->getKeyCode()) );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SDLK_F10:
|
|
if(event->isKeyPressedWithShiftEvent(event->getKeyCode()))
|
|
graphicsQualityShortcutSignal(false);
|
|
else
|
|
graphicsQualityShortcutSignal(true);
|
|
break;
|
|
|
|
case SDLK_F11:
|
|
verb = getWhitelistVerb("Toggle", "Full", "Screen");
|
|
break;
|
|
|
|
case SDLK_F12:
|
|
verb = getWhitelistVerb("Record", "Toggle", "");
|
|
break;
|
|
|
|
case SDLK_SYSREQ: // This is technically print ...... directx doesn't have a print key code :(
|
|
case SDLK_PRINT:
|
|
verb = getWhitelistVerb("", "Screen", "shot");
|
|
break;
|
|
case SDLK_LALT:
|
|
case SDLK_RALT:
|
|
RBX::GameBasicSettings::singleton().setFreeLook(true); // if we are in camlock, holding alt (option on mac) allows us to pan camera freely
|
|
break;
|
|
|
|
case SDLK_ESCAPE:
|
|
if (RBX::GuiService* guiService = RBX::ServiceProvider::find<RBX::GuiService>(this))
|
|
guiService->escapeKeyPressed();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
if (verb && verb->isEnabled()) {
|
|
Verb::doItWithChecks(verb, this);
|
|
if (!preventSinking)
|
|
return GuiResponse::sunk();
|
|
}
|
|
|
|
return GuiResponse::notSunk();
|
|
}
|
|
|
|
GuiResponse DataModel::processPlayerGui(const shared_ptr<InputObject>& event)
|
|
{
|
|
RBX::Network::Players* players = ServiceProvider::create<RBX::Network::Players>(this);
|
|
|
|
if (RBX::Network::Player* player = players->getLocalPlayer())
|
|
if (PlayerGui* playerGui = player->findFirstChildOfType<PlayerGui>())
|
|
return playerGui->process(event);
|
|
|
|
return GuiResponse::notSunk();
|
|
}
|
|
|
|
GuiResponse DataModel::processCameraCommands(const shared_ptr<InputObject>& event)
|
|
{
|
|
Verb* verb = NULL;
|
|
|
|
|
|
if (FFlag::UserAllCamerasInLua)
|
|
{
|
|
if (const Camera* camera = workspace->getConstCamera())
|
|
{
|
|
if (camera->hasClientPlayer())
|
|
{
|
|
return GuiResponse::notSunk();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!FFlag::UserBetterInertialScrolling)
|
|
{
|
|
switch(event->getUserInputType())
|
|
{
|
|
case InputObject::TYPE_MOUSEWHEEL:
|
|
{
|
|
if (const Camera* camera = workspace->getConstCamera())
|
|
{
|
|
if (camera->getCameraType() == Camera::CUSTOM_CAMERA)
|
|
{
|
|
if (RBX::Network::Players::findLocalPlayer(this))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (userInputService && !userInputService->isCtrlDown() && !userInputService->isShiftDown() && !userInputService->isAltDown())
|
|
{
|
|
if (event->isMouseWheelForward())
|
|
verb = workspace->getWhitelistVerb("Camera", "Zoom", "In");
|
|
else if(event->isMouseWheelBackward())
|
|
verb = workspace->getWhitelistVerb("Camera", "Zoom", "Out");
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (FFlag::UserBetterInertialScrolling)
|
|
{
|
|
if (event->isMouseWheelForward() || event->isMouseWheelBackward())
|
|
{
|
|
if (Camera* camera = workspace->getCamera())
|
|
{
|
|
camera->zoom(event->getPosition().z);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (verb && verb->isEnabled())
|
|
{
|
|
Verb::doItWithChecks(verb, this);
|
|
}
|
|
}
|
|
|
|
return GuiResponse::notSunk(); // don't sink this event when we fire it
|
|
}
|
|
|
|
void DataModel::setInitialScreenSize(RBX::Vector2 newScreenSize)
|
|
{
|
|
if (shared_ptr<RBX::ScreenGui> coreScreenGui = coreGuiService->getRobloxScreenGui())
|
|
{
|
|
coreScreenGui->setBufferedViewport(Rect2D(newScreenSize));
|
|
}
|
|
}
|
|
|
|
GuiResponse DataModel::processDevGamepadEvent(const shared_ptr<InputObject>& event)
|
|
{
|
|
if (GamepadService* gamepadService = ServiceProvider::find<RBX::GamepadService>(this))
|
|
{
|
|
return gamepadService->processDev(event);
|
|
}
|
|
|
|
return GuiResponse::notSunk();
|
|
}
|
|
|
|
GuiResponse DataModel::processCoreGamepadEvent(const shared_ptr<InputObject>& event)
|
|
{
|
|
if (GamepadService* gamepadService = ServiceProvider::find<RBX::GamepadService>(this))
|
|
{
|
|
return gamepadService->processCore(event);
|
|
}
|
|
|
|
return GuiResponse::notSunk();
|
|
}
|
|
|
|
GuiResponse DataModel::processProfilerEvent(const shared_ptr<InputObject>& event)
|
|
{
|
|
if (event->isMouseEvent())
|
|
{
|
|
unsigned int flags = Profiler::Flag_MouseMove;
|
|
|
|
int mouseX = event->getRawPosition().x;
|
|
int mouseY = event->getRawPosition().y;
|
|
int mouseWheel = 0;
|
|
int mouseButton = 0;
|
|
|
|
if (event->isMouseWheelEvent())
|
|
{
|
|
flags |= Profiler::Flag_MouseWheel;
|
|
mouseWheel = event->getRawPosition().z;
|
|
}
|
|
|
|
if (event->isMouseDownEvent())
|
|
{
|
|
flags |= Profiler::Flag_MouseDown;
|
|
mouseButton = event->getUserInputType() - InputObject::TYPE_MOUSEBUTTON1;
|
|
}
|
|
|
|
if (event->isMouseUpEvent())
|
|
{
|
|
flags |= Profiler::Flag_MouseUp;
|
|
mouseButton = event->getUserInputType() - InputObject::TYPE_MOUSEBUTTON1;
|
|
}
|
|
|
|
return Profiler::handleMouse(flags, mouseX, mouseY, mouseWheel, mouseButton) ? GuiResponse::sunk() : GuiResponse::notSunk();
|
|
}
|
|
|
|
if (event->isKeyDownEvent())
|
|
{
|
|
unsigned int ctrlOrCmd = KMOD_LCTRL | KMOD_RCTRL | KMOD_LMETA | KMOD_RMETA;
|
|
|
|
if (event->isKeyDownEvent(SDLK_F6) && (event->mod & ctrlOrCmd))
|
|
return Profiler::toggleVisible() ? GuiResponse::sunk() : GuiResponse::notSunk();
|
|
|
|
if (event->isKeyDownEvent(SDLK_p) && (event->mod & ctrlOrCmd))
|
|
return Profiler::togglePause() ? GuiResponse::sunk() : GuiResponse::notSunk();
|
|
}
|
|
|
|
return GuiResponse::notSunk();
|
|
}
|
|
|
|
GuiResponse DataModel::processGuiTarget(const shared_ptr<InputObject>& event)
|
|
{
|
|
GuiResponse response = GuiResponse::notSunk();
|
|
|
|
if (shared_ptr<Instance> guiTarget = guiTargetInstance.lock())
|
|
{
|
|
FASTLOG1(FLog::UserInputProfile, "Handing to GUI target: %p", guiTarget.get());
|
|
if (TextBox* textbox = fastDynamicCast<TextBox>(guiTarget.get()))
|
|
{
|
|
if (event->isKeyEvent() || event->isGamepadEvent())
|
|
{
|
|
response = textbox->process(event);
|
|
}
|
|
else if (!DFFlag::DontProcessMouseEventsForGuiTarget)
|
|
{
|
|
response = textbox->preProcess(event);
|
|
}
|
|
}
|
|
else if (GuiLayerCollector* guiCollector = fastDynamicCast<GuiLayerCollector>(guiTarget.get()))
|
|
{
|
|
response = guiCollector->process(event);
|
|
}
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
bool DataModel::processEvent(const shared_ptr<InputObject>& event)
|
|
{
|
|
RBXASSERT(event->getUserInputType() != InputObject::TYPE_NONE);
|
|
if (!isInitialized)
|
|
return false;
|
|
|
|
if (event->getUserInputType() == InputObject::TYPE_MOUSEMOVEMENT)
|
|
mouseStats.mouseMove.sample();
|
|
|
|
// Hack #2
|
|
switch(event->getUserInputType())
|
|
{
|
|
case InputObject::TYPE_MOUSEBUTTON2:
|
|
if (event->getUserInputState() == InputObject::INPUT_STATE_END) // button up
|
|
{
|
|
FASTLOG(FLog::UserInputProfile, "Cancelling Right mouse pan");
|
|
workspace->cancelRightMousePan();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
bool isInMenu = false;
|
|
if (RBX::GuiService* guiService = RBX::ServiceProvider::find<RBX::GuiService>(this))
|
|
{
|
|
isInMenu = guiService->getMenuOpen();
|
|
}
|
|
|
|
GuiResponse response = GuiResponse();
|
|
shared_ptr<Instance> guiTarget = guiTargetInstance.lock();
|
|
|
|
if (!guiTarget)
|
|
setSuppressNavKeys(false); // no target - make sure we no longer suppress asdf, which we do when a TextBox gets the focus/is target
|
|
|
|
FASTLOG(FLog::UserInputProfile, "Starting handing out events...");
|
|
|
|
if (event->getUserInputType() == InputObject::TYPE_MOUSEMOVEMENT)
|
|
{
|
|
mouseOverInteractable = weak_ptr<GuiObject>();
|
|
}
|
|
|
|
mouseOverGui = false;
|
|
if (!response.wasSunk())
|
|
{
|
|
FASTLOG(FLog::UserInputProfile, "Handing to Profiler");
|
|
response = processProfilerEvent(event);
|
|
}
|
|
if (DFFlag::ProcessAcceleratorsBeforeGUINavigation)
|
|
{
|
|
|
|
if (!isInMenu)
|
|
{
|
|
FASTLOG(FLog::UserInputProfile, "Handing to Accelerators");
|
|
response = processAccelerators(event);
|
|
}
|
|
}
|
|
|
|
if (!response.wasSunk())
|
|
{
|
|
response = processGuiTarget(event);
|
|
}
|
|
|
|
// todo: remove guiRoot when profiler has all the same stats
|
|
if (!response.wasSunk())
|
|
{
|
|
FASTLOG(FLog::UserInputProfile, "Handing to GUI root");
|
|
response = guiRoot->process(event);
|
|
}
|
|
|
|
if (!response.wasSunk() && (renderGuisActive || !DFFlag::AllowHideHudShortcut))
|
|
{
|
|
FASTLOG(FLog::UserInputProfile, "Handing to Core Gui");
|
|
response = coreGuiService->process(event);
|
|
mouseOverGui = response.getMouseWasOver();
|
|
}
|
|
|
|
if (!response.wasSunk() && event->isKeyDownEvent() && !suppressNavKeys)
|
|
{
|
|
if (GamepadService* gamepadService = RBX::ServiceProvider::create<GamepadService>(this))
|
|
{
|
|
FASTLOG(FLog::UserInputProfile, "Handing to gamepadService for keyboard event");
|
|
response = gamepadService->trySelectGuiObject(userInputService->getKeyboardGuiSelectionDirection(event));
|
|
}
|
|
}
|
|
|
|
if (!response.wasSunk())
|
|
{
|
|
FASTLOG(FLog::UserInputProfile, "Handing to processCoreGamepadEvent");
|
|
response = processCoreGamepadEvent(event);
|
|
}
|
|
|
|
if (!response.wasSunk())
|
|
{
|
|
FASTLOG(FLog::UserInputProfile, "Handing to ContextActionService Core Bindings");
|
|
response = contextActionService->processCoreBindings(event);
|
|
}
|
|
|
|
if (!DFFlag::ProcessAcceleratorsBeforeGUINavigation)
|
|
{
|
|
if (!response.wasSunk())
|
|
{
|
|
FASTLOG(FLog::UserInputProfile, "Handing to Accelerators");
|
|
response = processAccelerators(event);
|
|
}
|
|
}
|
|
|
|
if (!response.wasSunk() && (renderGuisActive || !DFFlag::AllowHideHudShortcut) && !isInMenu)
|
|
{
|
|
FASTLOG(FLog::UserInputProfile, "Handing to Player Gui");
|
|
response = processPlayerGui(event);
|
|
mouseOverGui = response.getMouseWasOver();
|
|
|
|
if ( response.getMouseWasOver())
|
|
{
|
|
if (GuiButton* responseButton = fastDynamicCast<GuiButton>(response.getTarget().get()))
|
|
{
|
|
mouseOverInteractable = weak_from(responseButton);
|
|
}
|
|
if (TextBox* responseBox = fastDynamicCast<TextBox>(response.getTarget().get()))
|
|
{
|
|
mouseOverInteractable = weak_from(responseBox);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!response.wasSunk() && !isInMenu)
|
|
{
|
|
FASTLOG(FLog::UserInputProfile, "Handing to processDevGamepadEvent");
|
|
response = processDevGamepadEvent(event);
|
|
}
|
|
|
|
if (!response.wasSunk())
|
|
{
|
|
FASTLOG(FLog::UserInputProfile, "Handing to ContextActionService Dev Bindings");
|
|
response = contextActionService->processDevBindings(event, isInMenu);
|
|
}
|
|
|
|
if (!response.wasSunk() && !isInMenu)
|
|
{
|
|
FASTLOG(FLog::UserInputProfile, "Handing to Camera Process");
|
|
response = processCameraCommands(event);
|
|
}
|
|
|
|
InputObject::UserInputType lastType = userInputService->getLastInputType();
|
|
if (GamepadService::getGamepadIntForEnum(lastType) == -1)
|
|
{
|
|
forceArrowCursor = (response.wasSunk() && guiTarget && (guiTarget.get() != workspace.get())) || mouseOverInteractable.lock();
|
|
}
|
|
|
|
const bool wasSunkBeforeHandingToTool = response.wasSunk();
|
|
|
|
if (!response.wasSunk() && !isInMenu && !event->isTouchEvent() && event->isPublicEvent())
|
|
{
|
|
FASTLOG(FLog::UserInputProfile, "Handing to Workspace");
|
|
response = workspace->process(event);
|
|
}
|
|
|
|
// Check for player idle (if we have a mouse event or a key was pressed, signal we aren't idle)
|
|
if (event->isMouseEvent() || event->isKeyDownEvent() || event->isGamepadEvent())
|
|
Network::Player::onLocalPlayerNotIdle(this);
|
|
|
|
if (DFFlag::DontProcessMouseEventsForGuiTarget)
|
|
{
|
|
// If a textbox is currently focused and the target is not that textbox then release focus from that textbox
|
|
if (TextBox* textbox = fastDynamicCast<TextBox>(guiTarget.get()))
|
|
{
|
|
if (textbox->getFocused() && (guiTarget != response.getTarget()))
|
|
{
|
|
textbox->releaseFocus(false, event);
|
|
}
|
|
}
|
|
}
|
|
|
|
guiTargetInstance = response.getTarget();
|
|
|
|
FASTLOG(FLog::UserInputProfile, "DM event processed");
|
|
|
|
return wasSunkBeforeHandingToTool;
|
|
}
|
|
|
|
void DataModel::processWorkspaceEvent(const shared_ptr<InputObject>& event)
|
|
{
|
|
GuiResponse response = GuiResponse();
|
|
|
|
FASTLOG(FLog::UserInputProfile, "Handing to Workspace via processWorkspaceEvent");
|
|
response = workspace->process(event);
|
|
}
|
|
|
|
bool DataModel::processInputObject(shared_ptr<InputObject> event)
|
|
{
|
|
RBXASSERT(isInitialized); // Show to David or Erik - threading issue
|
|
if (!isInitialized || !event)
|
|
return false;
|
|
|
|
bool processedEvent = false;
|
|
|
|
if (event->getUserInputType() != InputObject::TYPE_NONE)
|
|
processedEvent = processEvent(event);
|
|
|
|
InputObjectProcessed(event);
|
|
|
|
if (Network::Player* player = Network::Players::findLocalPlayer(this))
|
|
{
|
|
if (shared_ptr<Mouse> playerMouse = player->getMouse())
|
|
{
|
|
if(!processedEvent || event->getUserInputType() == InputObject::TYPE_MOUSEMOVEMENT)
|
|
{
|
|
playerMouse->update(event);
|
|
}
|
|
else if (event->isMouseEvent())
|
|
{
|
|
playerMouse->cacheInputObject(event);
|
|
}
|
|
}
|
|
}
|
|
|
|
RBXASSERT(isInitialized); // Show to David or Erik - threading issue
|
|
|
|
return processedEvent;
|
|
}
|
|
|
|
void DataModel::close()
|
|
{
|
|
Verb* verb = getWhitelistVerb("E", "xi", "t");
|
|
if (verb==NULL)
|
|
throw std::runtime_error("Couldn't find Exit Verb");
|
|
Verb::doItWithChecks(verb, NULL);
|
|
}
|
|
|
|
void DataModel::addCustomStat(std::string name, std::string value)
|
|
{
|
|
guiBuilder.addCustomStat(name, value);
|
|
}
|
|
|
|
void DataModel::removeCustomStat(std::string str)
|
|
{
|
|
guiBuilder.removeCustomStat(str);
|
|
}
|
|
|
|
void DataModel::writeStatsSettings()
|
|
{
|
|
guiBuilder.saveCustomStats();
|
|
}
|
|
|
|
|
|
void DataModel::clearContents(bool resettingSimulation)
|
|
{
|
|
// TODO: Should Service have a "clearContents()" function?
|
|
|
|
ScriptContext* sc = find<ScriptContext>();
|
|
if (sc)
|
|
{
|
|
if (DFFlag::CloseStatesBeforeChildRemoval)
|
|
{
|
|
FASTLOG(FLog::CloseDataModel, "Closing Script Context");
|
|
sc->closeStates(resettingSimulation);
|
|
ScriptContext::propScriptsDisabled.setValue(sc, false);
|
|
}
|
|
else
|
|
{
|
|
sc->setPreventNewConnections();
|
|
}
|
|
}
|
|
|
|
if (workspace)
|
|
{
|
|
// unlock terrain
|
|
if( workspace->getTerrain() ) workspace->getTerrain()->unlockParent();
|
|
|
|
workspace->setMouseCommand(shared_ptr<MouseCommand>());
|
|
|
|
if(workspace->getWorld() && workspace->getWorld()->getContactManager())
|
|
{
|
|
// spatial hash is slow to remove. do a special trick when we know we are removing everything.
|
|
workspace->getWorld()->getContactManager()->fastClear();
|
|
}
|
|
|
|
if(World* world = workspace->getWorld()){
|
|
FASTLOG3(FLog::CloseDataModel, "Clearing World -- %d Bodies, %d Points, %d Constraints", world->getNumBodies(), world->getNumPoints(), world->getNumConstraints());
|
|
FASTLOG3(FLog::CloseDataModel, "Clearing World -- %d HashNodes, %d MaxBucketSize, %d NumLinkCalls", world->getNumHashNodes(), world->getMaxBucketSize(), world->getNumLinkCalls());
|
|
FASTLOG3(FLog::CloseDataModel, "Clearing World -- %d Contacts, %d Joints, %d Primitives", world->getNumContacts(), world->getNumJoints(), world->getNumPrimitives());
|
|
}
|
|
|
|
FASTLOG3(FLog::CloseDataModel, "Clearing Workspace -- %d Instances, %d Parts, %d Scripts", workspace->countDescendantsOfType<Instance>(), workspace->countDescendantsOfType<PartInstance>(), workspace->countDescendantsOfType<BaseScript>());
|
|
// TODO: Should we simply remove children of ALL service???
|
|
workspace->removeAllChildren();
|
|
workspace->setCurrentCamera(NULL);
|
|
}
|
|
|
|
if (starterPackService){
|
|
FASTLOG1(FLog::CloseDataModel, "Clearing StarterPack -- %d Instances", starterPackService->countDescendantsOfType<Instance>());
|
|
starterPackService->removeAllChildren();
|
|
}
|
|
|
|
if (starterPlayerService){
|
|
FASTLOG1(FLog::CloseDataModel, "Clearing StarterPlayer -- %d Instances", starterPlayerService->countDescendantsOfType<Instance>());
|
|
starterPlayerService->removeAllChildren();
|
|
}
|
|
|
|
if (starterGuiService){
|
|
FASTLOG1(FLog::CloseDataModel, "Clearing StarterGui -- %d Instances", starterGuiService->countDescendantsOfType<Instance>());
|
|
starterGuiService->removeAllChildren();
|
|
}
|
|
|
|
// only remove core gui if we are closing doc
|
|
if (coreGuiService && !resettingSimulation){
|
|
FASTLOG1(FLog::CloseDataModel, "Clearing CoreGui -- %d Instances", coreGuiService->countDescendantsOfType<Instance>());
|
|
coreGuiService->removeAllChildren();
|
|
}
|
|
|
|
if (Teams* teams = ServiceProvider::find<Teams>(this)) {
|
|
FASTLOG1(FLog::CloseDataModel, "Clearing Teams -- %d Instances", teams->countDescendantsOfType<Instance>());
|
|
teams->removeAllChildren();
|
|
}
|
|
|
|
if (SpawnerService* ss = ServiceProvider::find<SpawnerService>(this))
|
|
ss->ClearContents();
|
|
|
|
if (ChangeHistoryService* ch = ServiceProvider::find<ChangeHistoryService>(this)){
|
|
FASTLOG1(FLog::CloseDataModel, "Clearing ChangeHistoryService -- %d waypoints ", ch->getWaypointCount());
|
|
ch->clearWaypoints();
|
|
}
|
|
|
|
if (Lighting* lighting = ServiceProvider::find<Lighting>(this)) {
|
|
FASTLOG1(FLog::CloseDataModel, "Clearing Lighting -- %d Instances", lighting->countDescendantsOfType<Instance>());
|
|
lighting->removeAllChildren();
|
|
}
|
|
|
|
if (Instance* service = ServiceProvider::find<JointsService>(this)) {
|
|
FASTLOG1(FLog::CloseDataModel, "Clearing JointsService -- %d Instances", service->countDescendantsOfType<Instance>());
|
|
service->removeAllChildren();
|
|
}
|
|
|
|
if (Instance* service = ServiceProvider::find<TestService>(this)) {
|
|
FASTLOG1(FLog::CloseDataModel, "Clearing TestService -- %d Instances", service->countDescendantsOfType<Instance>());
|
|
service->removeAllChildren();
|
|
}
|
|
|
|
if (Instance* service = ServiceProvider::find<ServerScriptService>(this)) {
|
|
FASTLOG1(FLog::CloseDataModel, "Clearing ServerScriptService -- %d Instances", service->countDescendantsOfType<Instance>());
|
|
service->removeAllChildren();
|
|
}
|
|
|
|
if (Instance* service = ServiceProvider::find<ReplicatedStorage>(this)) {
|
|
FASTLOG1(FLog::CloseDataModel, "Clearing ReplicatedStorage -- %d Instances", service->countDescendantsOfType<Instance>());
|
|
service->removeAllChildren();
|
|
}
|
|
|
|
if (Instance* service = ServiceProvider::find<RobloxReplicatedStorage>(this)) {
|
|
FASTLOG1(FLog::CloseDataModel, "Clearing RobloxReplicatedStorage -- %d Instances", service->countDescendantsOfType<Instance>());
|
|
service->removeAllChildren();
|
|
}
|
|
|
|
if (Instance* service = ServiceProvider::find<ReplicatedFirst>(this)) {
|
|
FASTLOG1(FLog::CloseDataModel, "Clearing ReplicatedFirst -- %d Instances", service->countDescendantsOfType<Instance>());
|
|
service->removeAllChildren();
|
|
}
|
|
|
|
if (Instance* service = ServiceProvider::find<ServerStorage>(this)) {
|
|
FASTLOG1(FLog::CloseDataModel, "Clearing ServerStorage -- %d Instances", service->countDescendantsOfType<Instance>());
|
|
service->removeAllChildren();
|
|
}
|
|
|
|
if (sc && !DFFlag::CloseStatesBeforeChildRemoval)
|
|
{
|
|
FASTLOG(FLog::CloseDataModel, "Closing Script Context");
|
|
sc->closeStates(resettingSimulation);
|
|
ScriptContext::propScriptsDisabled.setValue(sc, false);
|
|
}
|
|
}
|
|
|
|
|
|
static std::string report(double time)
|
|
{
|
|
if (time > 0.0)
|
|
{
|
|
char buffer[256];
|
|
std::string t = Log::formatTime(time);
|
|
int cpu = static_cast<int>(100 * time * 30.0);
|
|
sprintf(buffer, "%i %% %s (%.3gfps)", cpu, t.c_str(), 1.0/time);
|
|
return buffer;
|
|
}
|
|
else
|
|
return Log::formatTime(time);
|
|
}
|
|
|
|
void DataModel::setNetworkMetric(IMetric* metric)
|
|
{
|
|
networkMetric = metric;
|
|
}
|
|
|
|
double DataModel::getMetricValue(const std::string& metric) const
|
|
{
|
|
if (metric == "Render FPS")
|
|
{
|
|
return tempMetric->getMetricValue("Render FPS");
|
|
}
|
|
if (metric == "Render CPU")
|
|
{
|
|
return 100.0 * tempMetric->getMetricValue("Render Duty");
|
|
}
|
|
if (metric == "Render Time")
|
|
{
|
|
return 1000.0 * tempMetric->getMetricValue("Render Job Time");
|
|
}
|
|
if (metric == "Physics FPS")
|
|
{
|
|
return runService->smoothFps();
|
|
}
|
|
if (metric == "Physics CPU")
|
|
{
|
|
return 100.0 * runService->physicsCpuFraction();
|
|
}
|
|
if (metric == "Physics Time")
|
|
{
|
|
return 1000.0 * runService->physicsAverageStep();
|
|
}
|
|
if (metric == "Network Receive CPU")
|
|
{
|
|
return networkMetric ? networkMetric->getMetricValue("Network Receive CPU") : 0.0;
|
|
}
|
|
if (metric == "Network Receive Time")
|
|
{
|
|
return networkMetric ? networkMetric->getMetricValue("Network Receive Time") : 0.0;
|
|
}
|
|
if (metric == "Frame Time")
|
|
{
|
|
return tempMetric->getMetricValue("Delta Between Renders");
|
|
}
|
|
if (metric == "Effective FPS")
|
|
{
|
|
return 1000.0 / tempMetric->getMetricValue("Delta Between Renders");
|
|
}
|
|
|
|
Stats::StatsService* stats = find<Stats::StatsService>();
|
|
if (stats)
|
|
{
|
|
shared_ptr<Stats::Item> network = shared_from_polymorphic_downcast<Stats::Item>(stats->findFirstChildByName("Network"));
|
|
if (network && network->numChildren() > 1)
|
|
{
|
|
if (Instance* item = network->getChild(1))
|
|
{
|
|
if (metric == "Received Physics Packets")
|
|
{
|
|
return item->findFirstChildByName("Received Physics Packets")->fastDynamicCast<Stats::Item>()->getValue();
|
|
}
|
|
if (metric == "Data Ping")
|
|
{
|
|
return item->findFirstChildByName("Data Ping")->fastDynamicCast<Stats::Item>()->getValue();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return 0.0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return 0.0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return 0.0;
|
|
}
|
|
|
|
throw RBX::runtime_error("%s is not a valid metric.", metric.c_str());
|
|
}
|
|
|
|
std::string DataModel::getMetric(const std::string& valueName) const
|
|
{
|
|
World* world = workspace->getWorld();
|
|
|
|
if (valueName.compare(0, 11, "RenderStats") == 0) return tempMetric->getMetric(valueName);
|
|
if (valueName.compare(0, 3, "FRM") == 0) return RBX::format("%.3g", tempMetric->getMetricValue(valueName));
|
|
|
|
Stats::StatsService* stats = find<Stats::StatsService>();
|
|
if (stats)
|
|
{
|
|
shared_ptr<Stats::Item> network = shared_from_polymorphic_downcast<Stats::Item>(stats->findFirstChildByName("Network"));
|
|
if (network && network->numChildren() > 1)
|
|
{
|
|
if (Instance* item = network->getChild(1))
|
|
{
|
|
// raknet
|
|
if (Stats::Item* raknetItem = Instance::fastDynamicCast<Stats::Item>(item->findFirstChildByName("Stats")->findFirstChildByName(valueName)))
|
|
return RBX::format("%.0f", raknetItem->getValue());
|
|
|
|
// replicator
|
|
if (valueName == "RakNetPing")
|
|
{
|
|
return item->findFirstChildByName("Ping")->fastDynamicCast<Stats::Item>()->getStringValue();
|
|
}
|
|
else if (valueName == "GeneralStats")
|
|
{
|
|
return RBX::format("%.0f, %.2fms"
|
|
, item->findFirstChildByName("Send kBps")->findFirstChildByName("MtuSize")->fastDynamicCast<Stats::Item>()->getValue()
|
|
, item->findFirstChildByName("Data Ping")->fastDynamicCast<Stats::Item>()->getValue()
|
|
);
|
|
}
|
|
else if (valueName == "OutgoingStats")
|
|
{
|
|
return RBX::format("%.2f"
|
|
, item->findFirstChildByName("Send kBps")->fastDynamicCast<Stats::Item>()->getValue()
|
|
);
|
|
}
|
|
else if (valueName == "IncomingStats")
|
|
{
|
|
return RBX::format("%.2f, %.2f, %.0f"
|
|
, item->findFirstChildByName("Receive kBps")->fastDynamicCast<Stats::Item>()->getValue()
|
|
, item->findFirstChildByName("Received Packets")->fastDynamicCast<Stats::Item>()->getValue()
|
|
, item->findFirstChildByName("Packet Queue")->fastDynamicCast<Stats::Item>()->getValue()
|
|
);
|
|
}
|
|
else if (valueName == "InData")
|
|
{
|
|
Stats::Item* receivedDataPackets = item->findFirstChildByName("Received Data Packets")->fastDynamicCast<Stats::Item>();
|
|
float numPacket = receivedDataPackets->getValue();
|
|
float avgSize = receivedDataPackets->findFirstChildByName("Size")->fastDynamicCast<Stats::Item>()->getValue();
|
|
float kBps = numPacket * avgSize / 1000.f;
|
|
return RBX::format("%.2f, %.2f, %.2fB", kBps, numPacket, avgSize);
|
|
}
|
|
else if (valueName == "InPhysics")
|
|
{
|
|
Stats::Item* inPhysics = item->findFirstChildByName("Received Physics Packets")->fastDynamicCast<Stats::Item>();
|
|
float numPacket = inPhysics->getValue();
|
|
float avgSize = inPhysics->findFirstChildByName("Size")->fastDynamicCast<Stats::Item>()->getValue();
|
|
float kBps = numPacket * avgSize / 1000.f;
|
|
return RBX::format("%.2f, %.2f, %.2fB, %.2f", kBps, numPacket, avgSize
|
|
, inPhysics->findFirstChildByName("Average Lag")->fastDynamicCast<Stats::Item>()->getValue());
|
|
}
|
|
else if (valueName == "InTouches")
|
|
{
|
|
Stats::Item* inTouches = item->findFirstChildByName("Received Touch Packets")->fastDynamicCast<Stats::Item>();
|
|
float numPacket = inTouches->getValue();
|
|
float avgSize = inTouches->findFirstChildByName("Size")->fastDynamicCast<Stats::Item>()->getValue();
|
|
float kBps = numPacket * avgSize / 1000.f;
|
|
return RBX::format("%.2f, %.2f, %.2fB", kBps, numPacket, avgSize);
|
|
}
|
|
else if (valueName == "InClusters")
|
|
{
|
|
Stats::Item* inClusters = item->findFirstChildByName("Received Cluster Packets")->fastDynamicCast<Stats::Item>();
|
|
float numPacket = inClusters->getValue();
|
|
float avgSize = inClusters->findFirstChildByName("Size")->fastDynamicCast<Stats::Item>()->getValue();
|
|
float kBps = numPacket * avgSize / 1000.f;
|
|
return RBX::format("%.2f, %.2f, %.2fB", kBps, numPacket, avgSize);
|
|
}
|
|
else if (valueName == "OutPhysics")
|
|
{
|
|
Stats::Item* outPhysics = item->findFirstChildByName("Sent Physics Packets")->fastDynamicCast<Stats::Item>();
|
|
float numPacket = outPhysics->getValue();
|
|
float avgSize = outPhysics->findFirstChildByName("Size")->fastDynamicCast<Stats::Item>()->getValue();
|
|
float kBps = numPacket * avgSize / 1000.f;
|
|
return RBX::format("%.2f, %.2f, %.2fB, %.2f%%", kBps, numPacket, avgSize
|
|
, outPhysics->findFirstChildByName("Throttle")->fastDynamicCast<Stats::Item>()->getValue());
|
|
}
|
|
else if (valueName == "OutData")
|
|
{
|
|
Stats::Item* outProps = item->findFirstChildByName("Sent Data Packets")->fastDynamicCast<Stats::Item>();
|
|
float numPacket = outProps->getValue();
|
|
float avgSize = outProps->findFirstChildByName("Size")->fastDynamicCast<Stats::Item>()->getValue();
|
|
float kBps = numPacket * avgSize / 1000.f;
|
|
return RBX::format("%.2f, %.2f, %.2fB, %.2f%%", kBps, numPacket, avgSize, outProps->findFirstChildByName("Throttle")->fastDynamicCast<Stats::Item>()->getValue());
|
|
}
|
|
else if (valueName == "OutTouches")
|
|
{
|
|
Stats::Item* outTouches = item->findFirstChildByName("SentTouchPackets")->fastDynamicCast<Stats::Item>();
|
|
float numPacket = outTouches->getValue();
|
|
float avgSize = outTouches->findFirstChildByName("Size")->fastDynamicCast<Stats::Item>()->getValue();
|
|
float kBps = numPacket * avgSize / 1000.f;
|
|
return RBX::format("%.2f, %.2f, %.2fB, %.0f", kBps, numPacket, avgSize, outTouches->findFirstChildByName("WaitingTouches")->fastDynamicCast<Stats::Item>()->getValue());
|
|
}
|
|
else if (valueName == "OutClusters")
|
|
{
|
|
Stats::Item* outClusters = item->findFirstChildByName("Sent Cluster Packets")->fastDynamicCast<Stats::Item>();
|
|
float numPacket = outClusters->getValue();
|
|
float avgSize = outClusters->findFirstChildByName("Size")->fastDynamicCast<Stats::Item>()->getValue();
|
|
float kBps = numPacket * avgSize / 1000.f;
|
|
return RBX::format("%.2f, %.2f, %.2fB", kBps, numPacket, avgSize);
|
|
}
|
|
else if (valueName == "InPhysicsDetails")
|
|
{
|
|
std::string result = "";
|
|
if (Stats::Item* inPhysicsDetails = item->findFirstChildByName(valueName)->fastDynamicCast<Stats::Item>())
|
|
{
|
|
inPhysicsDetails->visitChildren(boost::bind(&GuiBuilder::buildNetworkStatsOutput, _1, &result));
|
|
}
|
|
return result;
|
|
}
|
|
else if (valueName == "OutPhysicsDetails")
|
|
{
|
|
std::string result = "";
|
|
if (Stats::Item* outPhysicsDetails = item->findFirstChildByName(valueName)->fastDynamicCast<Stats::Item>())
|
|
{
|
|
outPhysicsDetails->visitChildren(boost::bind(&GuiBuilder::buildNetworkStatsOutput, _1, &result));
|
|
}
|
|
return result;
|
|
}
|
|
else if (valueName == "InDataDetails")
|
|
{
|
|
std::string result = "";
|
|
if (Stats::Item* inDataDetails = item->findFirstChildByName("Received Data Types")->fastDynamicCast<Stats::Item>())
|
|
{
|
|
inDataDetails->visitChildren(boost::bind(&GuiBuilder::buildNetworkStatsOutput, _1, &result));
|
|
}
|
|
return result;
|
|
}
|
|
else if (valueName == "OutDataDetails")
|
|
{
|
|
std::string result = "";
|
|
if (Stats::Item* inDataDetails = item->findFirstChildByName("Send Data Types")->fastDynamicCast<Stats::Item>())
|
|
{
|
|
inDataDetails->visitChildren(boost::bind(&GuiBuilder::buildNetworkStatsOutput, _1, &result));
|
|
}
|
|
return result;
|
|
}
|
|
else if (valueName == "FreeMemory")
|
|
{
|
|
float result = RBX::NetworkSettings::singleton().getFreeMemoryMBytes();
|
|
return RBX::format("%.2fMB", result);
|
|
}
|
|
else if (valueName == "MemoryLevel")
|
|
{
|
|
int result = RBX::MemoryStats::slowCheckMemoryLevel(((RBX::MemoryStats::memsize_t)RBX::NetworkSettings::singleton().getExtraMemoryUsedInMB())*1024*1024);
|
|
return RBX::format("%d", result);
|
|
}
|
|
else if (valueName == "ReceivedStreamData")
|
|
{
|
|
std::string result = "";
|
|
if (Stats::Item* streamStats = item->findFirstChildByName("Received Stream Data")->fastDynamicCast<Stats::Item>())
|
|
{
|
|
streamStats->visitChildren(boost::bind(&GuiBuilder::buildSimpleStatsOutput, _1, &result));
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
shared_ptr<Stats::Item> httpQueue = shared_from_polymorphic_downcast<Stats::Item>(stats->findFirstChildByName("HttpQueue_ContentProvider"));
|
|
if (httpQueue)
|
|
{
|
|
if (valueName == "HttpTimeInQueue")
|
|
{
|
|
return RBX::format("%.2f msec", httpQueue->findFirstChildByName("Average time in queue")->fastDynamicCast<Stats::Item>()->getValue());
|
|
}
|
|
else if (valueName == "HttpProcessTime")
|
|
{
|
|
return RBX::format("%.2f msec", httpQueue->findFirstChildByName("Average process time")->fastDynamicCast<Stats::Item>()->getValue());
|
|
}
|
|
else if (valueName == "HttpSlowReq")
|
|
{
|
|
return RBX::format("%.0f", httpQueue->findFirstChildByName("Num slow requests")->fastDynamicCast<Stats::Item>()->getValue());
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (valueName == "physicsMode") {
|
|
return Network::Players::getDistributedPhysicsEnabled() ? "Experimental Physics" : "Standard Physics";
|
|
}
|
|
|
|
else if (valueName == "Physics") // This is the frequency at which physics job is actually executed
|
|
{
|
|
boost::format fmt("%.1f/s %.1f msec %d%%");
|
|
fmt % runService->smoothFps() % (1000.0 * runService->physicsAverageStep()) % (int)(100.0 * runService->physicsCpuFraction());
|
|
return fmt.str();
|
|
}
|
|
|
|
else if (valueName == "PhysicsReal") // This is the effective physics frame-rate considering steps skipped by environmental throttling
|
|
{
|
|
boost::format fmt("%.1f/s throttle@%d%%(%d)");
|
|
fmt % (runService->smoothFps() * world->getEnvironmentSpeed()) % (int)(100.0 * world->getEnvironmentSpeed()) % world->getFRMThrottle();
|
|
return fmt.str();
|
|
}
|
|
|
|
else if (valueName == "Heartbeat")
|
|
{
|
|
boost::format fmt("%.1f/s %.1f msec %d%%");
|
|
fmt % runService->heartbeatFps() % (1000.0 * runService->heartbeatAverageStep()) % (int)(100.0 * runService->heartbeatCpuFraction());
|
|
return fmt.str();
|
|
}
|
|
|
|
else if (valueName == "Network Send" || valueName == "Network Receive")
|
|
{
|
|
return networkMetric ? networkMetric->getMetric(valueName) : "";
|
|
}
|
|
|
|
else if (valueName == "Render")
|
|
{
|
|
boost::format fmt("%.1f/s %.1f msec %d%%");
|
|
fmt % tempMetric->getMetricValue("Render FPS") % (1000.0 * tempMetric->getMetricValue("Render Job Time")) % (int)(100.0 * tempMetric->getMetricValue("Render Duty"));
|
|
return fmt.str();
|
|
}
|
|
|
|
else if (valueName == "MouseMove")
|
|
{
|
|
boost::format fmt("%.1f/s -> %.1f/s");
|
|
fmt % mouseStats.osMouseMove.rate() % mouseStats.mouseMove.rate();
|
|
return fmt.str();
|
|
}
|
|
|
|
else if (valueName == "Effective FPS") return RBX::format("%.1f/s", 1000.0 / tempMetric->getMetricValue("Delta Between Renders"));
|
|
else if (valueName == "Render Nominal FPS") return RBX::format("%.1f/s", tempMetric->getMetricValue(valueName));
|
|
else if (valueName == "Delta Between Renders") return RBX::format("%.3g", tempMetric->getMetricValue(valueName));
|
|
else if (valueName == "Total Render") return RBX::format("%.3g", tempMetric->getMetricValue(valueName));
|
|
else if (valueName == "Render Prepare") return RBX::format("%.3g", tempMetric->getMetricValue(valueName));
|
|
else if (valueName == "Present Time") return RBX::format("%.3g", tempMetric->getMetricValue(valueName));
|
|
else if (valueName == "GPU Delay") return RBX::format("%.3g", tempMetric->getMetricValue(valueName));
|
|
|
|
else if (valueName == "RequestQueueSize") return StringConverter<int>::convertToString(ServiceProvider::create<ContentProvider>()->getRequestQueueSize());
|
|
|
|
else if (valueName == "drawId") return StringConverter<int>::convertToString(drawId);
|
|
else if (valueName == "worldId") return StringConverter<int>::convertToString(world->getWorldStepId());
|
|
else if (valueName == "Graphics Mode") return tempMetric->getMetric(valueName);
|
|
else if (valueName == "Particles") return StringConverter<int>::convertToString(static_cast<int>(tempMetric->getMetricValue(valueName)));
|
|
else if (valueName == "Shadow Casters") return StringConverter<int>::convertToString(static_cast<int>(tempMetric->getMetricValue(valueName)));
|
|
else if (valueName == "Anti-Aliasing") return tempMetric->getMetric(valueName);
|
|
else if (valueName == "Bevels") return tempMetric->getMetric(valueName);
|
|
else if (valueName == "Video Memory") return Log::formatMem(static_cast<int>(tempMetric->getMetricValue(valueName)));
|
|
|
|
else if (valueName == "Player Radius")
|
|
{
|
|
RBX::Network::Players* players = ServiceProvider::create<RBX::Network::Players>(this);
|
|
int numPlayers = players->getNumPlayers();
|
|
if (numPlayers == 0)
|
|
return std::string("N/A");
|
|
float sumRadius = 0.0f;
|
|
float sumMaxRaius = 0.0f;
|
|
shared_ptr<const Instances> myPlayers = players->getPlayers();
|
|
Instances::const_iterator iter = myPlayers->begin();
|
|
Instances::const_iterator end = myPlayers->end();
|
|
for (; iter!=end; ++iter)
|
|
{
|
|
RBX::Network::Player* player = boost::polymorphic_downcast<RBX::Network::Player*>(iter->get());
|
|
sumRadius += player->getSimulationRadius();
|
|
sumMaxRaius += player->getMaxSimulationRadius();
|
|
}
|
|
boost::format fmt("%d / %d");
|
|
fmt % static_cast<int>(sumRadius / numPlayers) % static_cast<int>(sumMaxRaius / numPlayers);
|
|
return fmt.str();
|
|
}
|
|
else if (valueName == "numPrimitives") return StringConverter<int>::convertToString(numPartInstances);
|
|
else if (valueName == "numMovingPrimitives") return StringConverter<int>::convertToString(workspace->getNumberMoving());
|
|
else if (valueName == "numJoints")
|
|
{
|
|
int numJoints = world->getNumJoints();
|
|
if (JointsService* jointsService = ServiceProvider::find<JointsService>(this))
|
|
{
|
|
numJoints += jointsService->numChildren();
|
|
}
|
|
return StringConverter<int>::convertToString(numJoints);
|
|
}
|
|
else if (valueName == "numInstances")
|
|
{
|
|
return StringConverter<int>::convertToString(Diagnostics::Countable<Instance>::getCount());
|
|
}
|
|
else if (valueName == "numContactsAll")
|
|
{
|
|
boost::format fmt("%d %d %d %d");
|
|
fmt % world->getNumContacts() %
|
|
world->getMetric(IWorldStage::NUM_CONTACTSTAGE_CONTACTS) %
|
|
world->getMetric(IWorldStage::NUM_STEPPING_CONTACTS) %
|
|
world->getMetric(IWorldStage::NUM_TOUCHING_CONTACTS);
|
|
return fmt.str();
|
|
}
|
|
else if (valueName == "numContacts") return StringConverter<int>::convertToString(world->getNumContacts());
|
|
else if (valueName == "numContactsInCollisionStage") return StringConverter<int>::convertToString(world->getMetric(IWorldStage::NUM_CONTACTSTAGE_CONTACTS));
|
|
else if (valueName == "numSteppingContacts") return StringConverter<int>::convertToString(world->getMetric(IWorldStage::NUM_STEPPING_CONTACTS));
|
|
else if (valueName == "numTouchingContacts") return StringConverter<int>::convertToString(world->getMetric(IWorldStage::NUM_TOUCHING_CONTACTS));
|
|
else if (valueName == "maxTreeDepth") return StringConverter<int>::convertToString(world->getMetric(IWorldStage::MAX_TREE_DEPTH));
|
|
else if (valueName == "contactPairHitRatio") return StringConverter<float>::convertToString(BlockBlockContact::pairHitRatio());
|
|
else if (valueName == "contactFeatureHitRatio") return StringConverter<float>::convertToString(BlockBlockContact::featureHitRatio());
|
|
|
|
else if (valueName == "numLinkCalls") return StringConverter<int>::convertToString(world->getNumLinkCalls());
|
|
else if (valueName == "numHashNodes") return StringConverter<int>::convertToString(world->getNumHashNodes());
|
|
else if (valueName == "maxBucketSize") return StringConverter<int>::convertToString(world->getMaxBucketSize());
|
|
else if (valueName == "environmentSpeed") return StringConverter<float>::convertToString(world->getEnvironmentSpeed());
|
|
|
|
|
|
else if (valueName == "Break Time" ||
|
|
valueName == "Assembler Time" ||
|
|
valueName == "Filter Time" ||
|
|
valueName == "UI Step" ||
|
|
valueName == "Broadphase" ||
|
|
valueName == "Collision" ||
|
|
valueName == "Wake" ||
|
|
valueName == "Sleep" ||
|
|
valueName == "Joint Update" ||
|
|
valueName == "Joint Sleep" ||
|
|
valueName == "Kernel Bodies" ||
|
|
valueName == "Kernel Connectors" )
|
|
{
|
|
if (stats)
|
|
{
|
|
shared_ptr<Stats::Item> workspace = shared_from_polymorphic_downcast<Stats::Item>(stats->findFirstChildByName("Workspace"));
|
|
if( workspace && workspace->numChildren() > 0)
|
|
{
|
|
shared_ptr<Stats::Item> dataModelStep = shared_from_polymorphic_downcast<Stats::Item>(workspace->findFirstChildByName("DataModel Step"));
|
|
if( dataModelStep && dataModelStep->numChildren() > 0)
|
|
{
|
|
shared_ptr<Stats::Item> workspaceStep = shared_from_polymorphic_downcast<Stats::Item>(dataModelStep->findFirstChildByName("Workspace Step"));
|
|
if( workspaceStep && workspaceStep->numChildren() > 0)
|
|
{
|
|
shared_ptr<Stats::Item> worldStep = shared_from_polymorphic_downcast<Stats::Item>(workspaceStep->findFirstChildByName("World Step"));
|
|
if (worldStep && worldStep->numChildren() > 0)
|
|
{
|
|
RBX::Instance* profilerItem = worldStep->findFirstChildByName(valueName);
|
|
RBX::Stats::Item* profilerStatItem = Instance::fastDynamicCast<RBX::Stats::Item>(profilerItem);
|
|
if(profilerStatItem)
|
|
return profilerStatItem->getStringValue();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// these are here for fun and to throw off the competition!
|
|
else if (valueName == "solverIterations") return StringConverter<int>::convertToString(world->getKernel()->fakeDeceptiveSolverIterations());
|
|
else if (valueName == "matrixSize") return StringConverter<int>::convertToString(world->getKernel()->fakeDeceptiveMatrixSize());
|
|
|
|
|
|
else if (valueName == "pGSSolverActive")
|
|
{
|
|
return StringConverter<bool>::convertToString(world->getUsingPGSSolver());
|
|
}
|
|
|
|
else if (valueName == "numBodiesAll")
|
|
{
|
|
boost::format fmt("%d %d %d %d %d %d");
|
|
fmt % world->getKernel()->numBodiesMax() %
|
|
world->getKernel()->numFreeFallBodies() %
|
|
world->getKernel()->numContactBodies() %
|
|
world->getKernel()->numJointBodies() %
|
|
world->getKernel()->numRealTimeBodies() %
|
|
world->getKernel()->numLeafBodies();
|
|
return fmt.str();
|
|
}
|
|
|
|
else if (valueName == "numConnectorsAll")
|
|
{
|
|
boost::format fmt("%d %d %d %d %d %d");
|
|
fmt % world->getKernel()->numConnectors() %
|
|
world->getKernel()->numContactConnectors() %
|
|
world->getKernel()->numJointConnectors() %
|
|
world->getKernel()->numRealTimeConnectors() %
|
|
world->getKernel()->numSecondPassConnectors() %
|
|
world->getKernel()->numHumanoidConnectors();
|
|
return fmt.str();
|
|
}
|
|
|
|
else if (valueName == "numBodies") return StringConverter<int>::convertToString(world->getKernel()->numBodies());
|
|
else if (valueName == "maxBodies") return StringConverter<int>::convertToString(world->getKernel()->numBodiesMax());
|
|
else if (valueName == "numLeafBodies") return StringConverter<int>::convertToString(world->getKernel()->numLeafBodies());
|
|
else if (valueName == "numConnectorsAndPoints")
|
|
{
|
|
boost::format fmt("%d %d");
|
|
fmt % world->getKernel()->numConnectors() % world->getKernel()->numPoints();
|
|
return fmt.str();
|
|
}
|
|
|
|
else if (valueName == "numIterations")
|
|
{
|
|
boost::format fmt("%d %d (%f %f)");
|
|
fmt % world->getKernel()->numMaxIterations() % world->getKernel()->numIterations()
|
|
% world->getKernel()->getMaxSolverError() % world->getKernel()->getSolverError();
|
|
return fmt.str();
|
|
}
|
|
|
|
else if (valueName == "numConstraints") return StringConverter<int>::convertToString(world->getKernel()->numConnectors());
|
|
else if (valueName == "numPoints") return StringConverter<int>::convertToString(world->getKernel()->numPoints());
|
|
else if (valueName == "percentConnectorsActive") return StringConverter<float>::convertToString(ContactConnector::percentActive());
|
|
|
|
else if (valueName == "energyBody") return StringConverter<float>::convertToString(world->getKernel()->bodyKineticEnergy());
|
|
else if (valueName == "energyConnector")return StringConverter<float>::convertToString(world->getKernel()->connectorSpringEnergy());
|
|
else if (valueName == "energyTotal") return StringConverter<float>::convertToString(world->getKernel()->totalKineticEnergy());
|
|
|
|
//RBXASSERT(0);
|
|
return "?";
|
|
}
|
|
|
|
DataModel* DataModel::get(Instance* context)
|
|
{
|
|
return context ? Instance::fastDynamicCast<DataModel>(context->getRootAncestor()) : NULL;
|
|
}
|
|
const DataModel* DataModel::get(const Instance* context)
|
|
{
|
|
return context ? Instance::fastDynamicCast<DataModel>(context->getRootAncestor()) : NULL;
|
|
}
|
|
|
|
static void appendJobInfo(DataModel* dataModel, shared_ptr<const TaskScheduler::Job> job, Reflection::ValueArray* result)
|
|
{
|
|
if (job->getArbiter().get()!=dataModel)
|
|
return;
|
|
|
|
|
|
shared_ptr<Reflection::ValueArray> info(rbx::make_shared<Reflection::ValueArray>());
|
|
info->push_back(job->name);
|
|
info->push_back(job->averageDutyCycle());
|
|
info->push_back(job->averageStepsPerSecond());
|
|
info->push_back(job->averageStepTime());
|
|
info->push_back(job->averageError());
|
|
info->push_back(job->isRunning());
|
|
|
|
result->push_back(shared_ptr<const RBX::Reflection::ValueArray>(info));
|
|
}
|
|
|
|
shared_ptr<const Reflection::ValueArray> DataModel::getJobsInfo()
|
|
{
|
|
// Returns some nice diagnostic information
|
|
shared_ptr<Reflection::ValueArray> result(rbx::make_shared<Reflection::ValueArray>());
|
|
{
|
|
shared_ptr<Reflection::ValueArray> info(rbx::make_shared<Reflection::ValueArray>());
|
|
info->push_back(std::string("name"));
|
|
info->push_back(std::string("averageDutyCycle"));
|
|
info->push_back(std::string("averageStepsPerSecond"));
|
|
info->push_back(std::string("averageStepTime"));
|
|
info->push_back(std::string("averageError"));
|
|
info->push_back(std::string("isRunning"));
|
|
|
|
result->push_back(shared_ptr<const RBX::Reflection::ValueArray>(info));
|
|
}
|
|
std::vector<boost::shared_ptr<const TaskScheduler::Job> > jobs;
|
|
TaskScheduler::singleton().getJobsInfo(jobs);
|
|
std::for_each(jobs.begin(), jobs.end(), boost::bind(appendJobInfo, this, _1, result.get()));
|
|
return result;
|
|
}
|
|
|
|
void DataModel::setCreatorID(int creatorID, CreatorType creatorType)
|
|
{
|
|
if(this->creatorID != creatorID)
|
|
{
|
|
this->creatorID = creatorID;
|
|
raisePropertyChanged(prop_creatorId);
|
|
}
|
|
if(this->creatorType != creatorType)
|
|
{
|
|
this->creatorType = creatorType;
|
|
raisePropertyChanged(prop_creatorType);
|
|
}
|
|
}
|
|
|
|
int DataModel::getPlaceIDOrZeroInStudio()
|
|
{
|
|
return runningInStudio ? 0 : getPlaceID();
|
|
}
|
|
|
|
static void updateFlagsOnPlaceFilter(const std::string& name, const std::string& varValue, void* context)
|
|
{
|
|
if (!varValue.empty())
|
|
{
|
|
std::string prefix = "PlaceFilter_";
|
|
std::size_t offset = name.find(prefix.c_str());
|
|
if (offset != std::string::npos)
|
|
{
|
|
offset += prefix.length();
|
|
DataModel* dm = reinterpret_cast<DataModel*>(context);
|
|
std::string flagName = name.substr(offset);
|
|
std::vector<std::string> places;
|
|
boost::split(places, varValue, boost::is_any_of(";"));
|
|
for (std::size_t i=1; i<places.size(); i++)
|
|
{
|
|
std::string placeIdStr = places[i];
|
|
if (dm->getPlaceID() == atoi(placeIdStr.c_str()))
|
|
{
|
|
FLog::SetValue(flagName, places[0] /*value*/, FASTVARTYPE_ANY, true);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DataModel::setPlaceID(int placeID, bool robloxPlace)
|
|
{
|
|
FASTLOG1(FLog::CloseDataModel, "Setting place ID %u", placeID);
|
|
RbxDbgInfo::AddPlace(placeID);
|
|
|
|
if(this->placeID != placeID)
|
|
{
|
|
this->placeID = placeID;
|
|
|
|
FLog::ForEachVariable(&updateFlagsOnPlaceFilter, this, FASTVARTYPE_ANY);
|
|
|
|
raisePropertyChanged(prop_placeId);
|
|
}
|
|
|
|
if(ScriptContext* sc = create<ScriptContext>())
|
|
sc->setRobloxPlace(robloxPlace);
|
|
|
|
Http::placeID = RBX::format("%d", placeID);
|
|
|
|
Analytics::setPlaceId(placeID);
|
|
RobloxGoogleAnalytics::setPlaceID(placeID);
|
|
}
|
|
|
|
|
|
static void gameStartInfoLoadedHelperSuccess(weak_ptr<DataModel> dm, std::string json)
|
|
{
|
|
FASTLOG(DFLog::R15Character, "DataModel::gameStartInfoLoadedHelperSuccess");
|
|
|
|
shared_ptr<DataModel> dm_shared = dm.lock();
|
|
if (dm_shared.get() == NULL)
|
|
return;
|
|
|
|
if (json.empty())
|
|
{
|
|
dm_shared->universeDataLoaded.set_value();
|
|
dm_shared->clearUniverseDataRequested();
|
|
return;
|
|
}
|
|
|
|
FASTLOGS(DFLog::R15Character, "DataModel::gameStartInfoLoadedHelperSuccess %s", json.c_str());
|
|
|
|
RBX::Reflection::Variant v;
|
|
if (RBX::WebParser::parseJSONObject(json, v))
|
|
{
|
|
bool forceR15 = v.cast<shared_ptr<const Reflection::ValueTable> >()->at("r15Morphing").cast<bool>();
|
|
dm_shared->setForceR15(forceR15);
|
|
}
|
|
|
|
dm_shared->universeDataLoaded.set_value();
|
|
dm_shared->clearUniverseDataRequested();
|
|
}
|
|
|
|
static void gameStartInfoLoadedHelperError(weak_ptr<DataModel> dm, std::string error)
|
|
{
|
|
FASTLOG(DFLog::R15Character, "DataModel::gameStartInfoLoadedHelperError");
|
|
|
|
shared_ptr<DataModel> dm_shared = dm.lock();
|
|
if (dm_shared.get() == NULL)
|
|
return;
|
|
|
|
FASTLOGS(DFLog::R15Character, "DataModel::gameStartInfoLoadedHelperError %s", error.c_str());
|
|
|
|
// didn't get settings, just do nothing for now
|
|
dm_shared->universeDataLoaded.set_value();
|
|
dm_shared->clearUniverseDataRequested();
|
|
}
|
|
|
|
void DataModel::setForceR15(bool v)
|
|
{
|
|
if (v != forceR15)
|
|
{
|
|
forceR15 = v;
|
|
raisePropertyChanged(prop_forceR15);
|
|
}
|
|
}
|
|
|
|
void DataModel::setCanRequestUniverseInfo(bool value)
|
|
{
|
|
FASTLOG(DFLog::R15Character, "DataModel::requestGameStartInfo setCanRequestUniverseInfo");
|
|
|
|
if (value != canRequestUniverseInfo)
|
|
{
|
|
FASTLOG(DFLog::R15Character, "DataModel::requestGameStartInfo setCanRequestUniverseInfo setting");
|
|
canRequestUniverseInfo = value;
|
|
if (canRequestUniverseInfo && universeDataRequested && Network::Players::backendProcessing(this))
|
|
{
|
|
requestGameStartInfo();
|
|
}
|
|
}
|
|
}
|
|
|
|
void DataModel::requestGameStartInfo()
|
|
{
|
|
FASTLOG(DFLog::R15Character, "DataModel::requestGameStartInfo universeDataRequested");
|
|
|
|
if (RBX::HttpRbxApiService* apiService = RBX::ServiceProvider::create<RBX::HttpRbxApiService>(this))
|
|
{
|
|
apiService->getAsync( RBX::format("universes/%d/game-start-info", universeId),false, RBX::PRIORITY_DEFAULT,
|
|
boost::bind(&gameStartInfoLoadedHelperSuccess, weak_from(this), _1),
|
|
boost::bind(&gameStartInfoLoadedHelperError, weak_from(this), _1));
|
|
}
|
|
}
|
|
|
|
|
|
void DataModel::setUniverseId(int uId)
|
|
{
|
|
FASTLOG1(DFLog::R15Character, "DataModel::setUniverseId %d", uId);
|
|
if (uId != universeId)
|
|
{
|
|
universeId = uId;
|
|
|
|
if (DFFlag::UseR15Character && Network::Players::backendProcessing(this))
|
|
{
|
|
FASTLOG(DFLog::R15Character, "DataModel::setUniverseId universeDataRequested");
|
|
universeDataLoaded = boost::promise<void>();
|
|
universeDataRequested = true;
|
|
if (canRequestUniverseInfo)
|
|
requestGameStartInfo();
|
|
}
|
|
}
|
|
}
|
|
|
|
void DataModel::setIsStudio(bool runningInStudio)
|
|
{
|
|
this->runningInStudio = runningInStudio;
|
|
}
|
|
|
|
void DataModel::setIsRunMode(bool value)
|
|
{
|
|
this->isStudioRunMode = value;
|
|
}
|
|
|
|
void DataModel::setPlaceVersion(int placeVersion)
|
|
{
|
|
FASTLOG1(FLog::CloseDataModel, "Setting place version %u", placeVersion);
|
|
if(this->placeVersion != placeVersion)
|
|
{
|
|
this->placeVersion = placeVersion;
|
|
raisePropertyChanged(prop_placeVersion);
|
|
}
|
|
}
|
|
bool DataModel::isGearTypeAllowed(GearType gearType)
|
|
{
|
|
return (allowedGearTypes & (1 << gearType)) != 0;
|
|
}
|
|
void DataModel::loadPlugins()
|
|
{
|
|
throw RBX::runtime_error("load plugins not supported");
|
|
}
|
|
void DataModel::setGenre(Genre genre)
|
|
{
|
|
if(this->genre != genre)
|
|
{
|
|
this->genre = genre;
|
|
raisePropertyChanged(prop_genre);
|
|
}
|
|
}
|
|
void DataModel::setGear(GearGenreSetting gearGenreSetting, int allowedGearTypes)
|
|
{
|
|
if(this->gearGenreSetting != gearGenreSetting)
|
|
{
|
|
this->gearGenreSetting = gearGenreSetting ;
|
|
raisePropertyChanged(prop_gearGenreSetting);
|
|
}
|
|
|
|
if(this->allowedGearTypes != allowedGearTypes)
|
|
{
|
|
this->allowedGearTypes = allowedGearTypes;
|
|
allowedGearTypeChanged();
|
|
}
|
|
}
|
|
|
|
std::string DataModel::getVIPServerId() const
|
|
{
|
|
if (RBX::Network::Players::clientIsPresent(this))
|
|
{
|
|
RBX::StandardOut::singleton()->printf(MESSAGE_WARNING, "VIPServerID checked on client, but only set on server.");
|
|
}
|
|
|
|
return vipServerId;
|
|
}
|
|
|
|
void DataModel::setVIPServerId(std::string value)
|
|
{
|
|
if (value != vipServerId)
|
|
{
|
|
vipServerId = value;
|
|
raisePropertyChanged(desc_VIPServerId);
|
|
}
|
|
}
|
|
|
|
int DataModel::getVIPServerOwnerId() const
|
|
{
|
|
if (RBX::Network::Players::clientIsPresent(this))
|
|
{
|
|
RBX::StandardOut::singleton()->printf(MESSAGE_WARNING, "VIPServerOwnerID checked on client, but only set on server.");
|
|
}
|
|
|
|
return vipServerOwnerId;
|
|
}
|
|
|
|
void DataModel::setVIPServerOwnerId(int value)
|
|
{
|
|
if (value != vipServerOwnerId)
|
|
{
|
|
vipServerOwnerId = value;
|
|
raisePropertyChanged(desc_VIPServerOwnerId);
|
|
}
|
|
}
|
|
|
|
void DataModel::gameLoaded()
|
|
{
|
|
if(!getIsGameLoaded())
|
|
setIsGameLoaded(true);
|
|
}
|
|
|
|
|
|
void DataModel::setJobsExtendedStatsWindow(double seconds)
|
|
{
|
|
TaskScheduler::singleton().setJobsExtendedStatsWindow(seconds);
|
|
}
|
|
|
|
static void appendJobExtendedStats(DataModel* dataModel, shared_ptr<const TaskScheduler::Job> job, Reflection::ValueArray* result)
|
|
{
|
|
if (job->getArbiter().get()!=dataModel)
|
|
return;
|
|
|
|
shared_ptr<Reflection::ValueArray> info(rbx::make_shared<Reflection::ValueArray>());
|
|
|
|
WindowAverageDutyCycle<>::Stats stats = job->getDutyCycleWindow().getStats();
|
|
info->push_back(job->name);
|
|
|
|
info->push_back(stats.time.average);
|
|
info->push_back(stats.time.variance);
|
|
info->push_back((double)stats.time.samples);
|
|
info->push_back(stats.interval.average.seconds());
|
|
info->push_back(stats.interval.variance.seconds());
|
|
info->push_back((double)stats.interval.samples);
|
|
info->push_back(stats.dutyfraction);
|
|
|
|
result->push_back(shared_ptr<const RBX::Reflection::ValueArray>(info));
|
|
|
|
}
|
|
|
|
shared_ptr<const Reflection::ValueArray> DataModel::getJobsExtendedStats()
|
|
{
|
|
// Returns some nice diagnostic information
|
|
shared_ptr<Reflection::ValueArray> result(rbx::make_shared<Reflection::ValueArray>());
|
|
{
|
|
|
|
shared_ptr<Reflection::ValueArray> info(rbx::make_shared<Reflection::ValueArray>());
|
|
info->push_back(std::string("name"));
|
|
info->push_back(std::string("time.average"));
|
|
info->push_back(std::string("time.variance"));
|
|
info->push_back(std::string("time.samples"));
|
|
info->push_back(std::string("interval.average"));
|
|
info->push_back(std::string("interval.variance"));
|
|
info->push_back(std::string("interval.samples"));
|
|
info->push_back(std::string("dutyfraction"));
|
|
|
|
result->push_back(shared_ptr<const RBX::Reflection::ValueArray>(info));
|
|
}
|
|
std::vector<boost::shared_ptr<const TaskScheduler::Job> > jobs;
|
|
TaskScheduler::singleton().getJobsInfo(jobs);
|
|
std::for_each(jobs.begin(), jobs.end(), boost::bind(appendJobExtendedStats, this, _1, result.get()));
|
|
return result;
|
|
}
|
|
|
|
static void getJobTimePeakFractionFunc(DataModel* dataModel, shared_ptr<const TaskScheduler::Job> job, std::string& jobname, double greaterThan, double* result)
|
|
{
|
|
if (job->getArbiter().get()!=dataModel)
|
|
return;
|
|
|
|
const WindowAverageDutyCycle<>& data = job->getDutyCycleWindow();
|
|
|
|
*result = data.countTimesGreaterThan(Time::Interval(greaterThan));
|
|
*result /= data.timesamples();
|
|
}
|
|
|
|
|
|
double DataModel::getJobTimePeakFraction(std::string jobname, double greaterThan)
|
|
{
|
|
double result = -1.0;
|
|
std::vector<boost::shared_ptr<const TaskScheduler::Job> > jobs;
|
|
TaskScheduler::singleton().getJobsByName(jobname, jobs);
|
|
std::for_each(jobs.begin(), jobs.end(), boost::bind(getJobTimePeakFractionFunc, this, _1, jobname, greaterThan, &result));
|
|
return result;
|
|
}
|
|
|
|
static void getJobIntervalPeakFractionFunc(DataModel* dataModel, shared_ptr<const TaskScheduler::Job> job, std::string& jobname, double greaterThan, double* result)
|
|
{
|
|
if (job->getArbiter().get()!=dataModel)
|
|
return;
|
|
|
|
const WindowAverageDutyCycle<>& data = job->getDutyCycleWindow();
|
|
|
|
*result = data.countIntervalsGreaterThan(Time::Interval(greaterThan));
|
|
*result /= data.intervalsamples();
|
|
}
|
|
|
|
|
|
double DataModel::getJobIntervalPeakFraction(std::string jobname, double greaterThan)
|
|
{
|
|
double result = -1.0;
|
|
std::vector<boost::shared_ptr<const TaskScheduler::Job> > jobs;
|
|
TaskScheduler::singleton().getJobsByName(jobname, jobs);
|
|
std::for_each(jobs.begin(), jobs.end(), boost::bind(getJobIntervalPeakFractionFunc, this, _1, jobname, greaterThan, &result));
|
|
return result;
|
|
}
|
|
|
|
/*override*/ void DataModel::onChildChanged(Instance* instance, const PropertyChanged& event) {
|
|
RBXASSERT(write_requested==1);
|
|
Super::onChildChanged(instance, event);
|
|
this->setDirty(true);
|
|
if (!itemChangedSignal.empty())
|
|
itemChangedSignal(shared_from(instance), &event.getDescriptor());
|
|
}
|
|
/*override*/ void DataModel::onDescendantAdded(Instance* instance) {
|
|
RBXASSERT(write_requested==1);
|
|
|
|
// keep track of how instances this datamodel has
|
|
numInstances++;
|
|
if (Instance::fastDynamicCast<PartInstance>(instance))
|
|
numPartInstances++;
|
|
|
|
Super::onDescendantAdded(instance);
|
|
this->setDirty(true);
|
|
}
|
|
/*override*/ void DataModel::onDescendantRemoving(const shared_ptr<Instance>& instance) {
|
|
RBXASSERT(write_requested==1);
|
|
|
|
numInstances--;
|
|
if (Instance::fastDynamicCast<PartInstance>(instance.get()))
|
|
numPartInstances--;
|
|
|
|
Super::onDescendantRemoving(instance);
|
|
this->setDirty(true);
|
|
}
|
|
|
|
int DataModel::getNumPlayers() const
|
|
{
|
|
const Network::Players* players = find<Network::Players>();
|
|
if (!players)
|
|
return 0;
|
|
return
|
|
players->getNumPlayers();
|
|
}
|
|
|
|
void DataModel::setUiMessage(std::string message)
|
|
{
|
|
uiMessage = message;
|
|
}
|
|
|
|
void DataModel::toggleToolsOff()
|
|
{
|
|
Verb* verb = getWhitelistVerb("Toggle", "Play", "Mode");
|
|
|
|
// Only toggle if tools are enabled, so user can't use it to turn tools back on
|
|
if (verb!=NULL && verb->isChecked()){
|
|
Verb::doItWithChecks(verb, NULL);
|
|
}
|
|
}
|
|
|
|
void DataModel::clearUiMessage()
|
|
{
|
|
uiMessage = "";
|
|
}
|
|
|
|
void DataModel::setUiMessageBrickCount()
|
|
{
|
|
uiMessage = "[[[progress]]]";
|
|
}
|
|
|
|
void DataModel::TakeScreenshotTask(weak_ptr<RBX::DataModel> weakDataModel)
|
|
{
|
|
if (shared_ptr<RBX::DataModel> dataModel = weakDataModel.lock())
|
|
{
|
|
dataModel->screenshotSignal();
|
|
}
|
|
}
|
|
|
|
void DataModel::ScreenshotReadyTask(weak_ptr<RBX::DataModel> weakDataModel, const std::string &filename)
|
|
{
|
|
if (shared_ptr<RBX::DataModel> dataModel = weakDataModel.lock())
|
|
{
|
|
dataModel->screenshotReadySignal(filename);
|
|
}
|
|
}
|
|
|
|
void DataModel::ScreenshotUploadTask(weak_ptr<RBX::DataModel> weakDataModel, bool finished)
|
|
{
|
|
if (shared_ptr<RBX::DataModel> dataModel = weakDataModel.lock())
|
|
{
|
|
dataModel->screenshotUploadSignal(finished);
|
|
}
|
|
}
|
|
|
|
void DataModel::ShowMessage(weak_ptr<RBX::DataModel> weakDataModel, int slot, const std::string &message, double duration)
|
|
{
|
|
if (shared_ptr<RBX::DataModel> dataModel = weakDataModel.lock())
|
|
{
|
|
if(shared_ptr<RBX::CoreGuiService> coreGuiService = shared_from(dataModel->find<RBX::CoreGuiService>()))
|
|
{
|
|
coreGuiService->displayOnScreenMessage(slot, message, duration);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool DataModel::currentThreadHasWriteLock() const
|
|
{
|
|
// TODO: Do we need a mutex around this?
|
|
return writeRequestingThread == GetCurrentThreadId();
|
|
}
|
|
|
|
bool DataModel::currentThreadHasWriteLock(Instance* context)
|
|
{
|
|
DataModel* dm = get(context);
|
|
return !dm || dm->currentThreadHasWriteLock();
|
|
}
|
|
|
|
Instance* DataModel::getLightingDeprecated() const
|
|
{
|
|
return ServiceProvider::find<Lighting>(this);
|
|
}
|
|
|
|
DataModel::scoped_write_request::scoped_write_request(Instance* context)
|
|
: dataModel(DataModel::get(context))
|
|
{
|
|
if (dataModel)
|
|
{
|
|
RBX::mutex::scoped_lock lock(dataModel->debugLock);
|
|
|
|
RBXASSERT(dataModel->write_requested == 0);
|
|
RBXASSERT(dataModel->read_requested == 0);
|
|
RBXASSERT(dataModel->writeRequestingThread == 0);
|
|
|
|
dataModel->write_requested = 1;
|
|
dataModel->writeRequestingThread = GetCurrentThreadId();
|
|
}
|
|
}
|
|
|
|
DataModel::scoped_write_request::~scoped_write_request()
|
|
{
|
|
if (dataModel)
|
|
{
|
|
RBX::mutex::scoped_lock lock(dataModel->debugLock);
|
|
|
|
RBXASSERT(dataModel->write_requested == 1);
|
|
RBXASSERT(dataModel->read_requested == 0);
|
|
RBXASSERT(dataModel->writeRequestingThread == GetCurrentThreadId());
|
|
|
|
dataModel->write_requested = 0;
|
|
dataModel->writeRequestingThread = 0;
|
|
}
|
|
}
|
|
|
|
// Place this code around tasks that write to a DataModel
|
|
DataModel::scoped_read_request::scoped_read_request(Instance* context)
|
|
: dataModel(DataModel::get(context))
|
|
{
|
|
if (dataModel)
|
|
{
|
|
RBX::mutex::scoped_lock lock(dataModel->debugLock);
|
|
|
|
RBXASSERT(dataModel->write_requested == 0);
|
|
|
|
dataModel->read_requested++;
|
|
}
|
|
}
|
|
|
|
DataModel::scoped_read_request::~scoped_read_request()
|
|
{
|
|
if (dataModel)
|
|
{
|
|
RBX::mutex::scoped_lock lock(dataModel->debugLock);
|
|
|
|
RBXASSERT(dataModel->write_requested == 0);
|
|
RBXASSERT(dataModel->read_requested > 0);
|
|
|
|
dataModel->read_requested--;
|
|
}
|
|
}
|
|
|
|
DataModel::scoped_write_transfer::scoped_write_transfer(Instance* context)
|
|
: dataModel(DataModel::get(context))
|
|
, oldWritingThread(0)
|
|
{
|
|
if (dataModel)
|
|
{
|
|
RBX::mutex::scoped_lock lock(dataModel->debugLock);
|
|
|
|
RBXASSERT(dataModel->write_requested == 1);
|
|
RBXASSERT(dataModel->writeRequestingThread != GetCurrentThreadId());
|
|
RBXASSERT(dataModel->writeRequestingThread != 0);
|
|
|
|
oldWritingThread = dataModel->writeRequestingThread;
|
|
dataModel->writeRequestingThread = GetCurrentThreadId();
|
|
}
|
|
}
|
|
|
|
DataModel::scoped_write_transfer::~scoped_write_transfer()
|
|
{
|
|
if (dataModel)
|
|
{
|
|
RBX::mutex::scoped_lock lock(dataModel->debugLock);
|
|
|
|
RBXASSERT(dataModel->write_requested == 1);
|
|
RBXASSERT(dataModel->writeRequestingThread == GetCurrentThreadId());
|
|
|
|
dataModel->writeRequestingThread = oldWritingThread;
|
|
}
|
|
}
|
|
|
|
|
|
unsigned int DataModel::allHackFlagsOredTogether() {
|
|
unsigned int result = 0;
|
|
#if !defined(RBX_STUDIO_BUILD)
|
|
VMProtectBeginMutation("18");
|
|
boost::mutex::scoped_lock l(hackFlagSetMutex);
|
|
|
|
for (boost::unordered_set<unsigned int>::iterator itr = hackFlagSet.begin();
|
|
itr != hackFlagSet.end(); ++itr) {
|
|
result |= *itr;
|
|
}
|
|
VMProtectEnd();
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
void DataModel::processHttpRequestResponseOnLock(DataModel *dataModel, std::string* response, std::exception* exception, boost::function<void(shared_ptr<std::string>,shared_ptr<std::exception> exception)> onLockAcquired)
|
|
{
|
|
if (dataModel == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
shared_ptr<std::string> responseCopy(response ? new std::string(*response) : NULL);
|
|
shared_ptr<std::exception> exceptionCopy(exception ? new std::exception(*exception) : NULL);
|
|
if (DFFlag::DataModelProcessHttpRequestResponseOnLockUseSubmitTask)
|
|
{
|
|
dataModel->submitTask(
|
|
boost::bind(onLockAcquired, responseCopy, exceptionCopy),
|
|
RBX::DataModelJob::TaskType::Write);
|
|
}
|
|
else
|
|
{
|
|
DataModel::LegacyLock lock(dataModel, DataModelJob::Write);
|
|
onLockAcquired(responseCopy, exceptionCopy);
|
|
}
|
|
}
|
|
|
|
} // namespace
|