/* 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 #include "rbx/RbxDbgInfo.h" #include #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 DataModel::loaderFunc = boost::function(dummyLoader); REFLECTION_BEGIN(); static Reflection::BoundFuncDesc func_loadPlugins(&DataModel::loadPlugins, "LoadPlugins", Security::Roblox); Reflection::EventDesc, 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(ContentId)> getContentFunctionOld(&DataModel::fetchAsset, "get", "url", Security::LocalUser); static Reflection::BoundFuncDesc(ContentId)> getContentFunction(&DataModel::fetchAsset, "GetObjects", "url", Security::Plugin); #endif static const Reflection::BoundYieldFuncDesc savePlaceAsyncFunction(&DataModel::savePlaceAsync, "SavePlace", "saveFilter", DataModel::SAVE_ALL, Security::None); static Reflection::BoundFuncDesc loadWorldFunction(&DataModel::loadWorld, "LoadWorld", "assetID", Security::LocalUser); static Reflection::BoundFuncDesc loadGameFunction(&DataModel::loadGame, "LoadGame", "assetID", Security::LocalUser); static Reflection::BoundFuncDesc loadFunction(&DataModel::loadContent, "Load", "url", Security::LocalUser); static Reflection::BoundFuncDesc saveFunction(&DataModel::save, "Save", "url", Security::Roblox); static Reflection::BoundFuncDesc setRemoteBuildMode(&DataModel::setRemoteBuildMode, "SetRemoteBuildMode", "buildModeEnabled", Security::LocalUser); static Reflection::BoundFuncDesc getRemoteBuildMode(&DataModel::getRemoteBuildMode, "GetRemoteBuildMode", Security::None); static Reflection::BoundFuncDesc setServerSaveUrlFunction(&DataModel::setServerSaveUrl, "SetServerSaveUrl", "url", Security::LocalUser); static Reflection::BoundFuncDesc 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 httpGetAsyncFunction(&DataModel::httpGetAsync, "HttpGetAsync", "url", kHttpPermission); static Reflection::BoundYieldFuncDesc httpPostAsyncFunction(&DataModel::httpPostAsync, "HttpPostAsync", "url", "data", "contentType", "*/*", kHttpPermission); static Reflection::BoundFuncDesc httpGetFunction(&DataModel::httpGet, "HttpGet", "url", "synchronous", false, kHttpPermission); static Reflection::BoundFuncDesc httpPostFunction(&DataModel::httpPost, "HttpPost", "url", "data", "synchronous", false, "contentType", "*/*", kHttpPermission); static Reflection::BoundFuncDesc()> getJobsInfo(&DataModel::getJobsInfo, "GetJobsInfo", Security::Plugin); static Reflection::BoundFuncDesc func_reportMeasurement(&DataModel::reportMeasurement, "ReportMeasurement", "id", "key1", "value1", "key2", "value2", Security::RobloxScript); static Reflection::BoundFuncDesc 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 sanitizeFunction(&DataModel::clearContents, "ClearContent", "resettingSimulation", Security::LocalUser); #endif static Reflection::BoundFuncDesc closeFunction(&DataModel::close, "Shutdown", Security::LocalUser); static Reflection::BoundFuncDesc toggleFunction(&DataModel::toggleToolsOff, "ToggleTools", Security::LocalUser); static Reflection::PropDescriptor prop_isPersonalServer("IsPersonalServer", category_State, &DataModel::getIsPersonalServer, &DataModel::setIsPersonalServer, Reflection::PropertyDescriptor::SCRIPTING, Security::RobloxScript); static Reflection::PropDescriptor prop_canSaveLocal("LocalSaveEnabled", category_State, &DataModel::canSaveLocal, NULL, Reflection::PropertyDescriptor::UI, Security::RobloxScript); static Reflection::BoundYieldFuncDesc saveToRobloxFunction(&DataModel::saveToRoblox, "SaveToRoblox", Security::RobloxScript); static Reflection::BoundCallbackDesc requestShutdownCallback("RequestShutdown", &DataModel::requestShutdownCallback, Security::RobloxScript); static Reflection::BoundFuncDesc finishShutdownFunction(&DataModel::completeShutdown, "FinishShutdown", "localSave", Security::RobloxScript); static Reflection::BoundFuncDesc func_SetUiMessage(&DataModel::setUiMessage, "SetMessage", "message", Security::LocalUser); static Reflection::BoundFuncDesc func_ClearUIMessage(&DataModel::clearUiMessage, "ClearMessage", Security::LocalUser); static Reflection::BoundFuncDesc func_SetUIMessageBrickCount(&DataModel::setUiMessageBrickCount, "SetMessageBrickCount", Security::LocalUser); static Reflection::BoundFuncDesc func_GetUIMessage(&DataModel::getUpdatedMessageBoxText, "GetMessage", Security::None); static Reflection::BoundFuncDesc()> getJobsExtendedStats(&DataModel::getJobsExtendedStats, "GetJobsExtendedStats", Security::Plugin); static Reflection::BoundFuncDesc getJobTimePeakFraction(&DataModel::getJobTimePeakFraction, "GetJobTimePeakFraction", "jobname", "greaterThan", Security::Plugin); static Reflection::BoundFuncDesc 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 setJobsExtendedStatsWindow(&DataModel::setJobsExtendedStatsWindow, "SetJobsExtendedStatsWindow", "seconds", Security::LocalUser); static Reflection::BoundFuncDesc setPlaceVersion(&DataModel::setPlaceVersion, "SetPlaceVersion", "placeId", Security::Plugin); static Reflection::BoundFuncDesc setPlaceId(&DataModel::setPlaceID, "SetPlaceId", "placeId", "robloxPlace", false, Security::Plugin); static Reflection::BoundFuncDesc setPlaceIdDeprecated(&DataModel::setPlaceID, "SetPlaceID", "placeID", "robloxPlace", false, Security::Plugin); static Reflection::BoundFuncDesc setGameInstanceId(&DataModel::setGameInstanceID, "SetGameInstanceId", "instanceID", Security::Plugin); static Reflection::BoundFuncDesc setUniverseId(&DataModel::setUniverseId, "SetUniverseId", "universeId", Security::Plugin); static Reflection::BoundFuncDesc setCreatorId(&DataModel::setCreatorID, "SetCreatorId", "creatorId", "creatorType", Security::Plugin); static Reflection::BoundFuncDesc setCreatorIdDeprecated(&DataModel::setCreatorID, "SetCreatorID", "creatorID", "creatorType", Security::Plugin); static Reflection::BoundFuncDesc func_setGenre(&DataModel::setGenre, "SetGenre", "genre", Security::Plugin); static Reflection::BoundFuncDesc func_setGear(&DataModel::setGear, "SetGearSettings", "genreRestriction", "allowedGenres", Security::Plugin); static Reflection::RefPropDescriptor prop_Workspace("Workspace", category_Data, &DataModel::getWorkspace, NULL, Reflection::PropertyDescriptor::UI); static Reflection::RefPropDescriptor prop_workspace("workspace", category_Data, &DataModel::getWorkspace, NULL, Reflection::PropertyDescriptor::Attributes::deprecated(prop_Workspace)); Reflection::RefPropDescriptor DataModel::prop_lighting("lighting", category_Data, &DataModel::getLightingDeprecated, NULL, Reflection::PropertyDescriptor::Attributes::deprecated()); static Reflection::PropDescriptor prop_placeId("PlaceId", category_State, &DataModel::getPlaceID, NULL, Reflection::PropertyDescriptor::UI); static Reflection::PropDescriptor prop_placeVersion("PlaceVersion", category_State, &DataModel::getPlaceVersion, NULL, Reflection::PropertyDescriptor::UI); static Reflection::PropDescriptor prop_creatorId("CreatorId", category_State, &DataModel::getCreatorID, NULL, Reflection::PropertyDescriptor::UI); static Reflection::PropDescriptor prop_forceR15("ForceR15", category_State, &DataModel::getForceR15, &DataModel::setForceR15, Reflection::PropertyDescriptor::REPLICATE_ONLY, Security::Roblox); static Reflection::EnumPropDescriptor prop_creatorType("CreatorType", category_State, &DataModel::getCreatorType, NULL, Reflection::PropertyDescriptor::UI); static Reflection::EnumPropDescriptor prop_genre("Genre", category_State, &DataModel::getGenre, NULL, Reflection::PropertyDescriptor::UI); static Reflection::EnumPropDescriptor prop_gearGenreSetting("GearGenreSetting", category_State, &DataModel::getGearGenreSetting, NULL, Reflection::PropertyDescriptor::UI); static Reflection::BoundFuncDesc func_isGearTypeAllowed(&DataModel::isGearTypeAllowed, "IsGearTypeAllowed", "gearType", Security::None); static Reflection::EventDesc event_allowedGearTypeChanged(&DataModel::allowedGearTypeChanged, "AllowedGearTypeChanged"); static Reflection::PropDescriptor prop_getJobId("JobId", "JobInfo", &DataModel::getJobId, NULL, Reflection::PropertyDescriptor::UI); static Reflection::BoundFuncDesc setScreenshotSEOInfoFunction(&DataModel::setScreenshotSEOInfo, "SetScreenshotInfo", "info", Security::LocalUser); static Reflection::BoundFuncDesc setVideoSEOInfoFunction(&DataModel::setVideoSEOInfo, "SetVideoInfo", "info", Security::LocalUser); static Reflection::EventDesc event_graphicsQualityChangeRequest(&DataModel::graphicsQualityShortcutSignal, "GraphicsQualityChangeRequest","betterQuality"); static Reflection::EventDesc event_GameLoaded(&DataModel::gameLoadedSignal, "Loaded"); static Reflection::BoundFuncDesc gameLoadedFunction(&DataModel::getIsGameLoaded, "IsLoaded", Security::None); static Reflection::BoundFuncDesc addStatFunction(&DataModel::addCustomStat, "AddStat", "displayName", "stat", Security::LocalUser); static Reflection::BoundFuncDesc removeStatFunction(&DataModel::removeCustomStat, "RemoveStat", "stat", Security::LocalUser); static Reflection::BoundFuncDesc writeStatSettingsFunction(&DataModel::writeStatsSettings, "SaveStats", Security::LocalUser); static Reflection::BoundAsyncCallbackDesc< DataModel, shared_ptr(void)> callback_onClose("OnClose", &DataModel::onCloseCallback, &DataModel::onCloseCallbackChanged); static Reflection::PropDescriptor desc_VIPServerId("VIPServerId", category_Data, &DataModel::getVIPServerId, NULL); static Reflection::BoundFuncDesc func_setVIPServerId(&DataModel::setVIPServerId, "SetVIPServerId", "newId", Security::LocalUser); static Reflection::PropDescriptor desc_VIPServerOwnerId("VIPServerOwnerId", category_Data, &DataModel::getVIPServerOwnerId, NULL); static Reflection::BoundFuncDesc 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::EnumDesc() :EnumDescriptor("CreatorType") { addPair(DataModel::CREATOR_USER, "User"); addPair(DataModel::CREATOR_GROUP, "Group"); } template<> DataModel::CreatorType& Variant::convert(void) { return genericConvert(); } template<> EnumDesc::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(void) { return genericConvert(); } template<> EnumDesc::EnumDesc() :EnumDescriptor("GearGenreSetting") { addPair(DataModel::GEAR_GENRE_ALL, "AllGenres"); addPair(DataModel::GEAR_GENRE_MATCH, "MatchingGenreOnly"); } template<> DataModel::GearGenreSetting& Variant::convert(void) { return genericConvert(); } template<> EnumDesc::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(void) { return genericConvert(); } template<> EnumDesc::EnumDesc() :EnumDescriptor("SaveFilter") { addPair(Instance::SAVE_ALL , "SaveAll"); addPair(Instance::SAVE_WORLD , "SaveWorld"); addPair(Instance::SAVE_GAME , "SaveGame"); } template<> Instance::SaveFilter& Variant::convert(void) { return genericConvert(); } } template<> bool RBX::StringConverter::convertToValue(const std::string& text, DataModel::CreatorType& value) { return Reflection::EnumDesc::singleton().convertToValue(text.c_str(),value); } template<> bool RBX::StringConverter::convertToValue(const std::string& text, DataModel::Genre& value) { return Reflection::EnumDesc::singleton().convertToValue(text.c_str(),value); } template<> bool RBX::StringConverter::convertToValue(const std::string& text, DataModel::GearGenreSetting& value) { return Reflection::EnumDesc::singleton().convertToValue(text.c_str(),value); } template<> bool RBX::StringConverter::convertToValue(const std::string& text, DataModel::GearType& value) { return Reflection::EnumDesc::singleton().convertToValue(text.c_str(),value); } template<> bool RBX::StringConverter::convertToValue(const std::string& text, DataModel::SaveFilter& value) { return Reflection::EnumDesc::singleton().convertToValue(text.c_str(),value); } class DataModel::GenericJob : public DataModelJob { public: weak_ptr const dataModel; rbx::timestamped_safe_queue tasks; GenericJob(shared_ptr 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 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 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(); 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 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(instance)) return !visit->getUploadUrl().empty(); return true; } bool DataModel::serverSavePlace(const SaveFilter saveFilter, boost::function resumeFunction, boost::function errorFunction) { // construct the url std::string parameters = "?assetId=" + boost::lexical_cast(placeID); parameters += "&isAppCreation=true"; std::string baseUrl = ServiceProvider::create(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 resumeFunction, boost::function 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(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, bool startHeartbeat, bool shouldShowLoadingScreen) { shared_ptr j; j = shared_ptr(new DataModel::GenericJob(dataModel, "Write Marshalled", DataModelJob::Write)); j->cyclicExecutive = false; dataModel->genericJobs[DataModelJob::Write] = j; TaskScheduler::singleton().add(j); j = shared_ptr(new DataModel::GenericJob(dataModel, "Read Marshalled", DataModelJob::Read)); j->cyclicExecutive = false; dataModel->genericJobs[DataModelJob::Read] = j; TaskScheduler::singleton().add(j); j = shared_ptr(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 source = CoreScript::fetchSource("LoadingScript")) { if (ScriptContext* sc = RBX::ServiceProvider::create(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::createDataModel(bool startHeartbeat, RBX::Verb* lockVerb, bool shouldShowLoadingScreen) { FASTLOG1(FLog::CloseDataModel, "Create DataModel - heartbeat(%d)", startHeartbeat); shared_ptr dataModel = Creatable::create(lockVerb); doDataModelSetup(dataModel, startHeartbeat, shouldShowLoadingScreen); return dataModel; } bool DataModel::onlyJobsLeftForThisArbiterAreGenericJobs() { std::vector > jobs; TaskScheduler::singleton().getJobsInfo(jobs); std::vector >::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) { 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(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(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 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 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) { 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 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::create(this)), guiRoot(Creatable::create()), 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(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); starterPlayerService = shared_from(ServiceProvider::create()); starterPackService = shared_from(ServiceProvider::create()); starterGuiService = shared_from(ServiceProvider::create()); coreGuiService = shared_from(ServiceProvider::create()); coreGuiService->createRobloxScreenGui(); if (TeleportService* ts = ServiceProvider::create(this)) { if (TeleportService::getCustomTeleportLoadingGui() && TeleportService::didTeleport()) { if (RBX::ScreenGui* loadingGui = Instance::fastDynamicCast(TeleportService::getCustomTeleportLoadingGui().get())) { ts->setTempCustomTeleportLoadingGui(loadingGui->clone(EngineCreator)); ts->getTempCustomTeleportLoadingGui()->setParent(coreGuiService->getRobloxScreenGui().get()); ts->setCustomTeleportLoadingGui(loadingGui->clone(EngineCreator)); } } } runService = shared_from(ServiceProvider::create()); ServiceProvider::create(); 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(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); ServiceProvider::create(); // We always need this, because it saves state userInputService = shared_from(ServiceProvider::create()); contextActionService = shared_from(ServiceProvider::create()); // store a ref since we use this in processInputObject ServiceProvider::create(); ServiceProvider::create(); } void DataModel::loadCoreScripts(const std::string &altStarterScript) { if (!areCoreScriptsLoaded) { areCoreScriptsLoaded = true; std::string baseUrl = ServiceProvider::create(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 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 >, 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, 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; boost::scoped_ptr writeTransfer; static void task(shared_ptr 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 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, 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, 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::tryGetGenericJob(DataModelJob::TaskType taskType) { RBX::mutex::scoped_lock lock(genericJobsLock); return genericJobs[taskType]; } shared_ptr DataModel::getGenericJob(DataModelJob::TaskType taskType) { shared_ptr 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 job = tryGetGenericJob(taskType)) { job->tasks.push(task); TaskScheduler::singleton().reschedule(job); } } void DataModel::raiseClose() { closingSignal(); closingLateSignal(); } void DataModel::traverseDataModelReporting(shared_ptr 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 resumeFunction, boost::function errorFunction) { if(canSave(this)){ if (Visit* visit = RBX::ServiceProvider::find(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 resumeFunction, boost::function errorFunction) { if(response){ resumeFunction(*response); } else{ errorFunction(exception->what()); } } void DataModel::doHttpGet(const std::string& url, boost::function resumeFunction, boost::function 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 resumeFunction, boost::function 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 resumeFunction, boost::function 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 resumeFunction, boost::function 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 DataModel::loadAssetIdIntoStream(int assetID) { // construct the url std::string parameters = "asset/?id=" + boost::lexical_cast(assetID); std::string url = ServiceProvider::create(this)->getBaseUrl() + parameters; StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "DataModel loading from: %s", url.c_str()); return std::auto_ptr(ServiceProvider::create(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 stream = loadAssetIdIntoStream(assetID); if(workspace) { workspace->clearTerrain(); workspace->removeAllChildren(); } if (Lighting* lighting = ServiceProvider::find(this)) lighting->removeAllChildren(); if (Soundscape::SoundService* service = ServiceProvider::find(this)) service->removeAllChildren(); if (ServerStorage* service = ServiceProvider::find(this)) service->removeAllChildren(); if (ReplicatedStorage* service = ServiceProvider::find(this)) service->removeAllChildren(); if (RobloxReplicatedStorage* service = ServiceProvider::find(this)) service->removeAllChildren(); if (ReplicatedFirst* service = ServiceProvider::find(this)) service->removeAllChildren(); Serializer serializer; serializer.load(*stream, this); LuaSourceContainer::blockingLoadLinkedScripts(create(), 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 stream = loadAssetIdIntoStream(assetID); if (ServerScriptService* service = ServiceProvider::find(this)) service->removeAllChildren(); if (starterGuiService) starterGuiService->removeAllChildren(); if (starterPackService) starterPackService->removeAllChildren(); if (starterPlayerService) starterPlayerService->removeAllChildren(); Serializer serializer; serializer.load(*stream, this); LuaSourceContainer::blockingLoadLinkedScripts(create(), 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 stream(ServiceProvider::create(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(), 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 DataModel::fetchAsset(ContentId contentId) { RBXASSERT(isInitialized); // If hit show to David or Erik - threading issue ContentProvider* cp = create(); std::auto_ptr stream(cp->getContent(contentId)); // Return all the new items in a table shared_ptr 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(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(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(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( 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(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 sortedAdorn; workspace->render3dAdorn(adorn); workspace->append3dSortedAdorn(sortedAdorn); RBX::Network::Players* players = ServiceProvider::create(this); if (RBX::Network::Player* player = players->getLocalPlayer()) { if (PlayerGui* playerGui = player->findFirstChildOfType()) { 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(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(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(); if(!cp) { RBXASSERT(false); return; } std::string response; if (RBX::HttpRbxApiService* apiService = RBX::ServiceProvider::find(this)) { apiService->get(RBX::format("game/GetAllowedExperimentalFeatures?placeId=%i", getPlaceID()), true, PRIORITY_EXTREME, response); } std::stringstream rawStream(response); shared_ptr 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& 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()->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(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(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(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& event) { RBX::Network::Players* players = ServiceProvider::create(this); if (RBX::Network::Player* player = players->getLocalPlayer()) if (PlayerGui* playerGui = player->findFirstChildOfType()) return playerGui->process(event); return GuiResponse::notSunk(); } GuiResponse DataModel::processCameraCommands(const shared_ptr& 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 coreScreenGui = coreGuiService->getRobloxScreenGui()) { coreScreenGui->setBufferedViewport(Rect2D(newScreenSize)); } } GuiResponse DataModel::processDevGamepadEvent(const shared_ptr& event) { if (GamepadService* gamepadService = ServiceProvider::find(this)) { return gamepadService->processDev(event); } return GuiResponse::notSunk(); } GuiResponse DataModel::processCoreGamepadEvent(const shared_ptr& event) { if (GamepadService* gamepadService = ServiceProvider::find(this)) { return gamepadService->processCore(event); } return GuiResponse::notSunk(); } GuiResponse DataModel::processProfilerEvent(const shared_ptr& 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& event) { GuiResponse response = GuiResponse::notSunk(); if (shared_ptr guiTarget = guiTargetInstance.lock()) { FASTLOG1(FLog::UserInputProfile, "Handing to GUI target: %p", guiTarget.get()); if (TextBox* textbox = fastDynamicCast(guiTarget.get())) { if (event->isKeyEvent() || event->isGamepadEvent()) { response = textbox->process(event); } else if (!DFFlag::DontProcessMouseEventsForGuiTarget) { response = textbox->preProcess(event); } } else if (GuiLayerCollector* guiCollector = fastDynamicCast(guiTarget.get())) { response = guiCollector->process(event); } } return response; } bool DataModel::processEvent(const shared_ptr& 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(this)) { isInMenu = guiService->getMenuOpen(); } GuiResponse response = GuiResponse(); shared_ptr 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(); } 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(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(response.getTarget().get())) { mouseOverInteractable = weak_from(responseButton); } if (TextBox* responseBox = fastDynamicCast(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(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& event) { GuiResponse response = GuiResponse(); FASTLOG(FLog::UserInputProfile, "Handing to Workspace via processWorkspaceEvent"); response = workspace->process(event); } bool DataModel::processInputObject(shared_ptr 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 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(); 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()); 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(), workspace->countDescendantsOfType(), workspace->countDescendantsOfType()); // 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()); starterPackService->removeAllChildren(); } if (starterPlayerService){ FASTLOG1(FLog::CloseDataModel, "Clearing StarterPlayer -- %d Instances", starterPlayerService->countDescendantsOfType()); starterPlayerService->removeAllChildren(); } if (starterGuiService){ FASTLOG1(FLog::CloseDataModel, "Clearing StarterGui -- %d Instances", starterGuiService->countDescendantsOfType()); starterGuiService->removeAllChildren(); } // only remove core gui if we are closing doc if (coreGuiService && !resettingSimulation){ FASTLOG1(FLog::CloseDataModel, "Clearing CoreGui -- %d Instances", coreGuiService->countDescendantsOfType()); coreGuiService->removeAllChildren(); } if (Teams* teams = ServiceProvider::find(this)) { FASTLOG1(FLog::CloseDataModel, "Clearing Teams -- %d Instances", teams->countDescendantsOfType()); teams->removeAllChildren(); } if (SpawnerService* ss = ServiceProvider::find(this)) ss->ClearContents(); if (ChangeHistoryService* ch = ServiceProvider::find(this)){ FASTLOG1(FLog::CloseDataModel, "Clearing ChangeHistoryService -- %d waypoints ", ch->getWaypointCount()); ch->clearWaypoints(); } if (Lighting* lighting = ServiceProvider::find(this)) { FASTLOG1(FLog::CloseDataModel, "Clearing Lighting -- %d Instances", lighting->countDescendantsOfType()); lighting->removeAllChildren(); } if (Instance* service = ServiceProvider::find(this)) { FASTLOG1(FLog::CloseDataModel, "Clearing JointsService -- %d Instances", service->countDescendantsOfType()); service->removeAllChildren(); } if (Instance* service = ServiceProvider::find(this)) { FASTLOG1(FLog::CloseDataModel, "Clearing TestService -- %d Instances", service->countDescendantsOfType()); service->removeAllChildren(); } if (Instance* service = ServiceProvider::find(this)) { FASTLOG1(FLog::CloseDataModel, "Clearing ServerScriptService -- %d Instances", service->countDescendantsOfType()); service->removeAllChildren(); } if (Instance* service = ServiceProvider::find(this)) { FASTLOG1(FLog::CloseDataModel, "Clearing ReplicatedStorage -- %d Instances", service->countDescendantsOfType()); service->removeAllChildren(); } if (Instance* service = ServiceProvider::find(this)) { FASTLOG1(FLog::CloseDataModel, "Clearing RobloxReplicatedStorage -- %d Instances", service->countDescendantsOfType()); service->removeAllChildren(); } if (Instance* service = ServiceProvider::find(this)) { FASTLOG1(FLog::CloseDataModel, "Clearing ReplicatedFirst -- %d Instances", service->countDescendantsOfType()); service->removeAllChildren(); } if (Instance* service = ServiceProvider::find(this)) { FASTLOG1(FLog::CloseDataModel, "Clearing ServerStorage -- %d Instances", service->countDescendantsOfType()); 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(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(); if (stats) { shared_ptr network = shared_from_polymorphic_downcast(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()->getValue(); } if (metric == "Data Ping") { return item->findFirstChildByName("Data Ping")->fastDynamicCast()->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(); if (stats) { shared_ptr network = shared_from_polymorphic_downcast(stats->findFirstChildByName("Network")); if (network && network->numChildren() > 1) { if (Instance* item = network->getChild(1)) { // raknet if (Stats::Item* raknetItem = Instance::fastDynamicCast(item->findFirstChildByName("Stats")->findFirstChildByName(valueName))) return RBX::format("%.0f", raknetItem->getValue()); // replicator if (valueName == "RakNetPing") { return item->findFirstChildByName("Ping")->fastDynamicCast()->getStringValue(); } else if (valueName == "GeneralStats") { return RBX::format("%.0f, %.2fms" , item->findFirstChildByName("Send kBps")->findFirstChildByName("MtuSize")->fastDynamicCast()->getValue() , item->findFirstChildByName("Data Ping")->fastDynamicCast()->getValue() ); } else if (valueName == "OutgoingStats") { return RBX::format("%.2f" , item->findFirstChildByName("Send kBps")->fastDynamicCast()->getValue() ); } else if (valueName == "IncomingStats") { return RBX::format("%.2f, %.2f, %.0f" , item->findFirstChildByName("Receive kBps")->fastDynamicCast()->getValue() , item->findFirstChildByName("Received Packets")->fastDynamicCast()->getValue() , item->findFirstChildByName("Packet Queue")->fastDynamicCast()->getValue() ); } else if (valueName == "InData") { Stats::Item* receivedDataPackets = item->findFirstChildByName("Received Data Packets")->fastDynamicCast(); float numPacket = receivedDataPackets->getValue(); float avgSize = receivedDataPackets->findFirstChildByName("Size")->fastDynamicCast()->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(); float numPacket = inPhysics->getValue(); float avgSize = inPhysics->findFirstChildByName("Size")->fastDynamicCast()->getValue(); float kBps = numPacket * avgSize / 1000.f; return RBX::format("%.2f, %.2f, %.2fB, %.2f", kBps, numPacket, avgSize , inPhysics->findFirstChildByName("Average Lag")->fastDynamicCast()->getValue()); } else if (valueName == "InTouches") { Stats::Item* inTouches = item->findFirstChildByName("Received Touch Packets")->fastDynamicCast(); float numPacket = inTouches->getValue(); float avgSize = inTouches->findFirstChildByName("Size")->fastDynamicCast()->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(); float numPacket = inClusters->getValue(); float avgSize = inClusters->findFirstChildByName("Size")->fastDynamicCast()->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(); float numPacket = outPhysics->getValue(); float avgSize = outPhysics->findFirstChildByName("Size")->fastDynamicCast()->getValue(); float kBps = numPacket * avgSize / 1000.f; return RBX::format("%.2f, %.2f, %.2fB, %.2f%%", kBps, numPacket, avgSize , outPhysics->findFirstChildByName("Throttle")->fastDynamicCast()->getValue()); } else if (valueName == "OutData") { Stats::Item* outProps = item->findFirstChildByName("Sent Data Packets")->fastDynamicCast(); float numPacket = outProps->getValue(); float avgSize = outProps->findFirstChildByName("Size")->fastDynamicCast()->getValue(); float kBps = numPacket * avgSize / 1000.f; return RBX::format("%.2f, %.2f, %.2fB, %.2f%%", kBps, numPacket, avgSize, outProps->findFirstChildByName("Throttle")->fastDynamicCast()->getValue()); } else if (valueName == "OutTouches") { Stats::Item* outTouches = item->findFirstChildByName("SentTouchPackets")->fastDynamicCast(); float numPacket = outTouches->getValue(); float avgSize = outTouches->findFirstChildByName("Size")->fastDynamicCast()->getValue(); float kBps = numPacket * avgSize / 1000.f; return RBX::format("%.2f, %.2f, %.2fB, %.0f", kBps, numPacket, avgSize, outTouches->findFirstChildByName("WaitingTouches")->fastDynamicCast()->getValue()); } else if (valueName == "OutClusters") { Stats::Item* outClusters = item->findFirstChildByName("Sent Cluster Packets")->fastDynamicCast(); float numPacket = outClusters->getValue(); float avgSize = outClusters->findFirstChildByName("Size")->fastDynamicCast()->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()) { 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()) { 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()) { 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()) { 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()) { streamStats->visitChildren(boost::bind(&GuiBuilder::buildSimpleStatsOutput, _1, &result)); } return result; } } } shared_ptr httpQueue = shared_from_polymorphic_downcast(stats->findFirstChildByName("HttpQueue_ContentProvider")); if (httpQueue) { if (valueName == "HttpTimeInQueue") { return RBX::format("%.2f msec", httpQueue->findFirstChildByName("Average time in queue")->fastDynamicCast()->getValue()); } else if (valueName == "HttpProcessTime") { return RBX::format("%.2f msec", httpQueue->findFirstChildByName("Average process time")->fastDynamicCast()->getValue()); } else if (valueName == "HttpSlowReq") { return RBX::format("%.0f", httpQueue->findFirstChildByName("Num slow requests")->fastDynamicCast()->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::convertToString(ServiceProvider::create()->getRequestQueueSize()); else if (valueName == "drawId") return StringConverter::convertToString(drawId); else if (valueName == "worldId") return StringConverter::convertToString(world->getWorldStepId()); else if (valueName == "Graphics Mode") return tempMetric->getMetric(valueName); else if (valueName == "Particles") return StringConverter::convertToString(static_cast(tempMetric->getMetricValue(valueName))); else if (valueName == "Shadow Casters") return StringConverter::convertToString(static_cast(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(tempMetric->getMetricValue(valueName))); else if (valueName == "Player Radius") { RBX::Network::Players* players = ServiceProvider::create(this); int numPlayers = players->getNumPlayers(); if (numPlayers == 0) return std::string("N/A"); float sumRadius = 0.0f; float sumMaxRaius = 0.0f; shared_ptr 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(iter->get()); sumRadius += player->getSimulationRadius(); sumMaxRaius += player->getMaxSimulationRadius(); } boost::format fmt("%d / %d"); fmt % static_cast(sumRadius / numPlayers) % static_cast(sumMaxRaius / numPlayers); return fmt.str(); } else if (valueName == "numPrimitives") return StringConverter::convertToString(numPartInstances); else if (valueName == "numMovingPrimitives") return StringConverter::convertToString(workspace->getNumberMoving()); else if (valueName == "numJoints") { int numJoints = world->getNumJoints(); if (JointsService* jointsService = ServiceProvider::find(this)) { numJoints += jointsService->numChildren(); } return StringConverter::convertToString(numJoints); } else if (valueName == "numInstances") { return StringConverter::convertToString(Diagnostics::Countable::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::convertToString(world->getNumContacts()); else if (valueName == "numContactsInCollisionStage") return StringConverter::convertToString(world->getMetric(IWorldStage::NUM_CONTACTSTAGE_CONTACTS)); else if (valueName == "numSteppingContacts") return StringConverter::convertToString(world->getMetric(IWorldStage::NUM_STEPPING_CONTACTS)); else if (valueName == "numTouchingContacts") return StringConverter::convertToString(world->getMetric(IWorldStage::NUM_TOUCHING_CONTACTS)); else if (valueName == "maxTreeDepth") return StringConverter::convertToString(world->getMetric(IWorldStage::MAX_TREE_DEPTH)); else if (valueName == "contactPairHitRatio") return StringConverter::convertToString(BlockBlockContact::pairHitRatio()); else if (valueName == "contactFeatureHitRatio") return StringConverter::convertToString(BlockBlockContact::featureHitRatio()); else if (valueName == "numLinkCalls") return StringConverter::convertToString(world->getNumLinkCalls()); else if (valueName == "numHashNodes") return StringConverter::convertToString(world->getNumHashNodes()); else if (valueName == "maxBucketSize") return StringConverter::convertToString(world->getMaxBucketSize()); else if (valueName == "environmentSpeed") return StringConverter::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 workspace = shared_from_polymorphic_downcast(stats->findFirstChildByName("Workspace")); if( workspace && workspace->numChildren() > 0) { shared_ptr dataModelStep = shared_from_polymorphic_downcast(workspace->findFirstChildByName("DataModel Step")); if( dataModelStep && dataModelStep->numChildren() > 0) { shared_ptr workspaceStep = shared_from_polymorphic_downcast(dataModelStep->findFirstChildByName("Workspace Step")); if( workspaceStep && workspaceStep->numChildren() > 0) { shared_ptr worldStep = shared_from_polymorphic_downcast(workspaceStep->findFirstChildByName("World Step")); if (worldStep && worldStep->numChildren() > 0) { RBX::Instance* profilerItem = worldStep->findFirstChildByName(valueName); RBX::Stats::Item* profilerStatItem = Instance::fastDynamicCast(profilerItem); if(profilerStatItem) return profilerStatItem->getStringValue(); } } } } } } // these are here for fun and to throw off the competition! else if (valueName == "solverIterations") return StringConverter::convertToString(world->getKernel()->fakeDeceptiveSolverIterations()); else if (valueName == "matrixSize") return StringConverter::convertToString(world->getKernel()->fakeDeceptiveMatrixSize()); else if (valueName == "pGSSolverActive") { return StringConverter::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::convertToString(world->getKernel()->numBodies()); else if (valueName == "maxBodies") return StringConverter::convertToString(world->getKernel()->numBodiesMax()); else if (valueName == "numLeafBodies") return StringConverter::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::convertToString(world->getKernel()->numConnectors()); else if (valueName == "numPoints") return StringConverter::convertToString(world->getKernel()->numPoints()); else if (valueName == "percentConnectorsActive") return StringConverter::convertToString(ContactConnector::percentActive()); else if (valueName == "energyBody") return StringConverter::convertToString(world->getKernel()->bodyKineticEnergy()); else if (valueName == "energyConnector")return StringConverter::convertToString(world->getKernel()->connectorSpringEnergy()); else if (valueName == "energyTotal") return StringConverter::convertToString(world->getKernel()->totalKineticEnergy()); //RBXASSERT(0); return "?"; } DataModel* DataModel::get(Instance* context) { return context ? Instance::fastDynamicCast(context->getRootAncestor()) : NULL; } const DataModel* DataModel::get(const Instance* context) { return context ? Instance::fastDynamicCast(context->getRootAncestor()) : NULL; } static void appendJobInfo(DataModel* dataModel, shared_ptr job, Reflection::ValueArray* result) { if (job->getArbiter().get()!=dataModel) return; shared_ptr info(rbx::make_shared()); 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(info)); } shared_ptr DataModel::getJobsInfo() { // Returns some nice diagnostic information shared_ptr result(rbx::make_shared()); { shared_ptr info(rbx::make_shared()); 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(info)); } std::vector > 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(context); std::string flagName = name.substr(offset); std::vector places; boost::split(places, varValue, boost::is_any_of(";")); for (std::size_t i=1; igetPlaceID() == 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()) sc->setRobloxPlace(robloxPlace); Http::placeID = RBX::format("%d", placeID); Analytics::setPlaceId(placeID); RobloxGoogleAnalytics::setPlaceID(placeID); } static void gameStartInfoLoadedHelperSuccess(weak_ptr dm, std::string json) { FASTLOG(DFLog::R15Character, "DataModel::gameStartInfoLoadedHelperSuccess"); shared_ptr 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 >()->at("r15Morphing").cast(); dm_shared->setForceR15(forceR15); } dm_shared->universeDataLoaded.set_value(); dm_shared->clearUniverseDataRequested(); } static void gameStartInfoLoadedHelperError(weak_ptr dm, std::string error) { FASTLOG(DFLog::R15Character, "DataModel::gameStartInfoLoadedHelperError"); shared_ptr 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(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(); 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 job, Reflection::ValueArray* result) { if (job->getArbiter().get()!=dataModel) return; shared_ptr info(rbx::make_shared()); 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(info)); } shared_ptr DataModel::getJobsExtendedStats() { // Returns some nice diagnostic information shared_ptr result(rbx::make_shared()); { shared_ptr info(rbx::make_shared()); 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(info)); } std::vector > 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 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 > 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 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 > 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(instance)) numPartInstances++; Super::onDescendantAdded(instance); this->setDirty(true); } /*override*/ void DataModel::onDescendantRemoving(const shared_ptr& instance) { RBXASSERT(write_requested==1); numInstances--; if (Instance::fastDynamicCast(instance.get())) numPartInstances--; Super::onDescendantRemoving(instance); this->setDirty(true); } int DataModel::getNumPlayers() const { const Network::Players* players = find(); 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 weakDataModel) { if (shared_ptr dataModel = weakDataModel.lock()) { dataModel->screenshotSignal(); } } void DataModel::ScreenshotReadyTask(weak_ptr weakDataModel, const std::string &filename) { if (shared_ptr dataModel = weakDataModel.lock()) { dataModel->screenshotReadySignal(filename); } } void DataModel::ScreenshotUploadTask(weak_ptr weakDataModel, bool finished) { if (shared_ptr dataModel = weakDataModel.lock()) { dataModel->screenshotUploadSignal(finished); } } void DataModel::ShowMessage(weak_ptr weakDataModel, int slot, const std::string &message, double duration) { if (shared_ptr dataModel = weakDataModel.lock()) { if(shared_ptr coreGuiService = shared_from(dataModel->find())) { 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(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::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,shared_ptr exception)> onLockAcquired) { if (dataModel == NULL) { return; } shared_ptr responseCopy(response ? new std::string(*response) : NULL); shared_ptr 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