diff options
-rw-r--r-- | LibOVR/Src/OVR_Linux_HMDDevice.cpp | 11 | ||||
-rw-r--r-- | LibOVR/Src/OVR_Linux_HMDDevice.h | 12 | ||||
-rw-r--r-- | LibOVR/Src/OVR_Profile.cpp | 1013 | ||||
-rw-r--r-- | LibOVR/Src/OVR_Profile.h | 385 |
4 files changed, 705 insertions, 716 deletions
diff --git a/LibOVR/Src/OVR_Linux_HMDDevice.cpp b/LibOVR/Src/OVR_Linux_HMDDevice.cpp index cce37d5..52f537d 100644 --- a/LibOVR/Src/OVR_Linux_HMDDevice.cpp +++ b/LibOVR/Src/OVR_Linux_HMDDevice.cpp @@ -234,14 +234,13 @@ Profile* HMDDeviceCreateDesc::GetProfileAddRef() const { // Create device may override profile name, so get it from there is possible. ProfileManager* profileManager = GetManagerImpl()->GetProfileManager(); - ProfileType profileType = GetProfileType(); const char * profileName = pDevice ? ((HMDDevice*)pDevice)->GetProfileName() : - profileManager->GetDefaultProfileName(profileType); + profileManager->GetDefaultProfileName(); return profileName ? - profileManager->LoadProfile(profileType, profileName) : - profileManager->GetDeviceDefaultProfile(profileType); + profileManager->LoadProfile(profileName) : + profileManager->GetDefaultProfile(); } @@ -341,7 +340,7 @@ bool HMDDevice::Initialize(DeviceBase* parent) // Initialize user profile to default for device. ProfileManager* profileManager = GetManager()->GetProfileManager(); - ProfileName = profileManager->GetDefaultProfileName(getDesc()->GetProfileType()); + ProfileName = profileManager->GetDefaultProfileName(); return true; } @@ -372,7 +371,7 @@ bool HMDDevice::SetProfileName(const char* name) ProfileName.Clear(); return 0; } - if (GetManager()->GetProfileManager()->HasProfile(getDesc()->GetProfileType(), name)) + if (GetManager()->GetProfileManager()->HasProfile(name)) { ProfileName = name; return true; diff --git a/LibOVR/Src/OVR_Linux_HMDDevice.h b/LibOVR/Src/OVR_Linux_HMDDevice.h index d0585d1..2350958 100644 --- a/LibOVR/Src/OVR_Linux_HMDDevice.h +++ b/LibOVR/Src/OVR_Linux_HMDDevice.h @@ -84,10 +84,10 @@ public: virtual bool GetDeviceInfo(DeviceInfo* info) const; // Requests the currently used default profile. This profile affects the - // settings reported by HMDInfo. + // settings reported by HMDInfo. Profile* GetProfileAddRef() const; - ProfileType GetProfileType() const + ProfileDeviceType GetProfileType() const { return (HResolution >= 1920) ? Profile_RiftDKHD : Profile_RiftDK1; } @@ -120,26 +120,26 @@ public: // HMDDevice represents an Oculus HMD device unit. An instance of this class // is typically created from the DeviceManager. -// After HMD device is created, we its sensor data can be obtained by +// After HMD device is created, we its sensor data can be obtained by // first creating a Sensor object and then wrappig it in SensorFusion. class HMDDevice : public DeviceImpl<OVR::HMDDevice> { public: HMDDevice(HMDDeviceCreateDesc* createDesc); - ~HMDDevice(); + ~HMDDevice(); virtual bool Initialize(DeviceBase* parent); virtual void Shutdown(); // Requests the currently used default profile. This profile affects the - // settings reported by HMDInfo. + // settings reported by HMDInfo. virtual Profile* GetProfile() const; virtual const char* GetProfileName() const; virtual bool SetProfileName(const char* name); // Query associated sensor. - virtual OVR::SensorDevice* GetSensor(); + virtual OVR::SensorDevice* GetSensor(); protected: HMDDeviceCreateDesc* getDesc() const { return (HMDDeviceCreateDesc*)pCreateDesc.GetPtr(); } diff --git a/LibOVR/Src/OVR_Profile.cpp b/LibOVR/Src/OVR_Profile.cpp index 3be43db..bf8906e 100644 --- a/LibOVR/Src/OVR_Profile.cpp +++ b/LibOVR/Src/OVR_Profile.cpp @@ -5,9 +5,9 @@ Filename : OVR_Profile.cpp Content : Structs and functions for loading and storing device profile settings Created : February 14, 2013 Notes : - + Profiles are used to store per-user settings that can be transferred and used - across multiple applications. For example, player IPD can be configured once + across multiple applications. For example, player IPD can be configured once and reused for a unified experience across games. Configuration and saving of profiles can be accomplished in game via the Profile API or by the official Oculus Configuration Utility. @@ -21,12 +21,13 @@ otherwise accompanies this software in either electronic or hard copy form. ************************************************************************************/ #include "OVR_Profile.h" -#include "OVR_JSON.h" +#include "OVR_Log.h" #include "Kernel/OVR_Types.h" #include "Kernel/OVR_SysFile.h" #include "Kernel/OVR_Allocator.h" #include "Kernel/OVR_Array.h" - +#include <json/json.h> +#include <fstream> #ifdef OVR_OS_WIN32 #include <Shlobj.h> #else @@ -40,8 +41,33 @@ otherwise accompanies this software in either electronic or hard copy form. #endif -#define PROFILE_VERSION 1.0 -#define MAX_PROFILE_MAJOR_VERSION 1 + +#define PROFILE_VERSION 2 +#define MAX_PROFILE_MAJOR_VERSION 2 + +// Many hard coded strings used in numerous locations have been +// repositioned here, so that there's no chance of a misspelling +// causing a problem. Not every string has been moved, but most of the +// repeated ones have. +#define KEY_PROFILE_VERSION "Oculus Profile Version" +#define KEY_CURRENT_PROFILE "CurrentProfile" +#define KEY_PROFILES "Profiles" +#define KEY_DEVICES "Devices" +#define KEY_GENDER "Gender" +#define KEY_PLAYER_HEIGHT "PlayerHeight" +#define KEY_IPD "IPD" +#define KEY_STRABISMUS_CORRECTION "StrabismusCorrection" + +#define KEY_LL "LL" +#define KEY_LR "LR" +#define KEY_RL "RL" +#define KEY_RR "RR" +#define KEY_EYECUP "EyeCup" +#define EPSILON 0.00001f +// 5'10" inch man +#define DEFAULT_HEIGHT 1.778f +#define DEFAULT_IPD 0.064f + namespace OVR { @@ -56,7 +82,7 @@ String GetBaseOVRPath(bool create_dir) TCHAR data_path[MAX_PATH]; SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, NULL, 0, data_path); path = String(data_path); - + path += "/Oculus"; if (create_dir) @@ -67,11 +93,11 @@ String GetBaseOVRPath(bool create_dir) DWORD attrib = GetFileAttributes(wpath); bool exists = attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY); if (!exists) - { + { CreateDirectory(wpath, NULL); } } - + #elif defined(OVR_OS_MAC) const char* home = getenv("HOME"); @@ -92,11 +118,21 @@ String GetBaseOVRPath(bool create_dir) } #else - - passwd* pwd = getpwuid(getuid()); - const char* home = pwd->pw_dir; - path = home; - path += "/.config/Oculus"; + // Updated the config folder location logic to rely on the + // XDG specification for config locations (the XDK location was being + // used anyway, but was hardcoded). This is analgous to using + // SHGetFolderPath in the windows implementation, rather than + // hardcoding %HOME%/AppData/Local + const char * config_home = getenv("XDG_CONFIG_HOME"); + if (NULL != config_home) { + path = config_home; + } else { + // only if XDG_CONFIG_HOME is unses does the specification say to + // fallback on the default of $HOME/.config + path = getenv("HOME"); + path += "/.config"; + } + path += "/Oculus"; if (create_dir) { // Create the Oculus directory if it doesn't exist @@ -129,7 +165,7 @@ String GetProfilePath(bool create_dir) ProfileManager::ProfileManager() { Changed = false; - CacheDevice = Profile_Unknown; + Loaded = false; } ProfileManager::~ProfileManager() @@ -146,410 +182,423 @@ ProfileManager* ProfileManager::Create() return new ProfileManager(); } -Profile* ProfileManager::CreateProfileObject(const char* user, - ProfileType device, - const char** device_name) -{ - Lock::Locker lockScope(&ProfileLock); - - Profile* profile = NULL; - switch (device) - { - case Profile_GenericHMD: - *device_name = NULL; - profile = new HMDProfile(Profile_GenericHMD, user); - break; - case Profile_RiftDK1: - *device_name = "RiftDK1"; - profile = new RiftDK1Profile(user); - break; - case Profile_RiftDKHD: - *device_name = "RiftDKHD"; - profile = new RiftDKHDProfile(user); - break; - case Profile_Unknown: - break; - } - - return profile; -} - - // Clear the local profile cache void ProfileManager::ClearCache() { Lock::Locker lockScope(&ProfileLock); - ProfileCache.Clear(); - CacheDevice = Profile_Unknown; + Loaded = false; } +// Profile loader is an intermediary that allows me to break down the +// serialization into smaller pieces that work directly against the +// new native JSON container type. This can't be done in the +// ProfileManager because it can't declare functions referencing the +// new type without polluting the header with knowledge of the JSON +// implementation +class ProfileLoader { +public: + static void loadHmd(HmdDevice & device, const Json::Value & node) { + if (node.isNull()) { + return; + } + if (node.isMember(KEY_LL)) { + device.LL = node.get(KEY_LL, 0).asInt(); + } + if (node.isMember(KEY_LR)) { + device.LR = node.get(KEY_LR, 0).asInt(); + } + if (node.isMember(KEY_RL)) { + device.RL = node.get(KEY_RL, 0).asInt(); + } + if (node.isMember(KEY_RR)) { + device.RR = node.get(KEY_RR, 0).asInt(); + } + } + + static void loadRift(RiftDevice & device, const Json::Value & node) { + if (node.isNull()) { + return; + } + String eyeCups = node.get(KEY_EYECUP, "A").asCString(); + char c = eyeCups.GetSize() ? eyeCups.GetCharAt(0) : 'A'; + switch (eyeCups.GetCharAt(0)) { + case 'A': + device.EyeCups = EyeCupType::EyeCup_A; + break; + case 'B': + device.EyeCups = EyeCupType::EyeCup_B; + break; + case 'C': + device.EyeCups = EyeCupType::EyeCup_C; + break; + } + loadHmd(device, node); + } + + // TODO migrate all the string constants up + static void loadV1Profile(Profile & out, const Json::Value & node) { + if (node.isNull()) { + return; + } + String gender = node.get(KEY_GENDER, "Unknown").asCString(); + if (gender == "Male") { + out.Gender = Profile::GenderType::Gender_Male; + } else if (gender == "Female") { + out.Gender = Profile::GenderType::Gender_Female; + } else { + out.Gender = Profile::GenderType::Gender_Unspecified; + } + + out.PlayerHeight = node.get(KEY_PLAYER_HEIGHT, DEFAULT_HEIGHT).asFloat(); + out.IPD = node.get(KEY_IPD, DEFAULT_IPD).asFloat(); + loadHmd(out.Generic, node.get("GenericHMD", Json::Value::null)); + loadRift(out.RiftDK1, node.get("RiftDK1", Json::Value::null)); + loadRift(out.RiftDKHD, node.get("RiftDKHD", Json::Value::null)); + } + + static void loadProfile(Profile & out, const Json::Value & node) { + if (node.isNull()) { + return; + } + loadV1Profile(out, node); + if (node.isMember(KEY_STRABISMUS_CORRECTION)) { + loadQuaternion(out.StrabismusCorrection, node[KEY_STRABISMUS_CORRECTION]); + } + } + + static void loadQuaternion(Quatf & out, const Json::Value & node) { + if (node.isNull()) { + return; + } + out.x = node.get("X", 0).asInt(); + out.y = node.get("Y", 0).asInt(); + out.z = node.get("Z", 0).asInt(); + out.w = node.get("W", 1).asInt(); + } + + static void saveQuaternion(Json::Value & out, const Quatf & q) { + out["X"] = q.x; + out["Y"] = q.y; + out["Z"] = q.z; + out["W"] = q.w; + } + + // TODO implement, and migrate the Devices.json parsing code to use this + static void loadMatrix(Matrix4f & out, const Json::Value & node) { + } + + // + // The general pattern on writing Json is to write the node only if + // it differs from the default, and to explicitly remove the node if it + // is the same as the default. This is important, because the load / save + // mechanism is designed to preserve any pre-existing content and not + // to touch any fields it's not aware of. + // + // Therefore, failing to write out a default value doesn't mean there's + // not already a non-default value there, hence the explicit removes. + // It gets a little verbose but the result is greater extensibility of the + // profile data by third parties. + static void writeHmdDevice(Json::Value & out, const HmdDevice & device) { + if (device.LL != 0) { + out[KEY_LL] = device.LL; + } else { + out.removeMember(KEY_LL); + } + if (device.LR != 0) { + out[KEY_LR] = device.LR; + } else { + out.removeMember(KEY_LR); + } + if (device.RL != 0) { + out[KEY_RL] = device.RL; + } else { + out.removeMember(KEY_RL); + } + if (device.RR != 0) { + out[KEY_RR] = device.RR; + } else { + out.removeMember(KEY_RR); + } + } + + static void writeRiftDevice(Json::Value & deviceNode, const RiftDevice & device) { + switch (device.EyeCups) + { + case EyeCup_B: deviceNode[KEY_EYECUP] = "B"; break; + case EyeCup_C: deviceNode[KEY_EYECUP] = "C"; break; + // A is the default, so no need to serialize it + case EyeCup_A: deviceNode.removeMember(KEY_EYECUP); break; + } + writeHmdDevice(deviceNode, device); + } + + // Applying the "remove nodes if they're empty" logic here is a little + // arduous, but it makes the resulting JSON cleaner + static void updateDeviceProfile(Json::Value & parent, const HmdDevice & device) { + String name; + Json::Value newChild; + switch (device.GetDeviceType()) { + case Profile_RiftDK1: + name = "RiftDK1"; + newChild = parent[name]; + writeRiftDevice(newChild, (RiftDevice&)device); + break; + + case Profile_RiftDKHD: + name = "RiftDKHD"; + newChild = parent[name]; + writeRiftDevice(newChild, (RiftDevice&)device); + break; + + case Profile_GenericHMD: + name = "GenericHMD"; + newChild = parent[name]; + writeHmdDevice(newChild, device); + break; + } + + // Don't write empty children + if (newChild.getMemberNames().size()) { + parent[name] = newChild; + } else { + parent.removeMember(name); + } + } + + static void writeProfile(Json::Value & parent, const Profile & profile) { + Json::Value & out = parent[profile.Name]; + + switch (profile.GetGender()) { + case Profile::Gender_Male: + out[KEY_GENDER] = "Male"; + break; + case Profile::Gender_Female: + out[KEY_GENDER] = "Female"; + break; + default: + out.removeMember(KEY_GENDER); + } + + // Epsilon is 10 micrometers. Smaller than a human hair + if (abs(profile.PlayerHeight - DEFAULT_HEIGHT) > EPSILON) { + out[KEY_PLAYER_HEIGHT] = profile.PlayerHeight; + } else { + out.removeMember(KEY_PLAYER_HEIGHT); + } + + if (abs(profile.IPD - DEFAULT_IPD) > EPSILON) { + out[KEY_IPD] = profile.IPD; + } else { + out.removeMember(KEY_IPD); + } + + if (profile.StrabismusCorrection.Distance(Quatf()) > EPSILON) { + saveQuaternion(out[KEY_STRABISMUS_CORRECTION], + profile.StrabismusCorrection); + } else { + out.removeMember(KEY_STRABISMUS_CORRECTION); + } + + Json::Value & devicesNode = out[KEY_DEVICES]; + updateDeviceProfile(devicesNode, profile.Generic); + updateDeviceProfile(devicesNode, profile.RiftDK1); + updateDeviceProfile(devicesNode, profile.RiftDKHD); + if (!devicesNode.getMemberNames().size()) { + out.removeMember(KEY_DEVICES); + } + } +}; + +// Use of STL IO is potentially avoidable with jsoncpp, if you wanted +// to load the file into a String instead and use the char* interface for +// parsing JSON in memory. Linking to C++ standard library is unavoidable +// though, but it might not take much work to remove iostream usage from +// the jsoncpp lib and make it use std::string only. +bool parseJsonFile(const String & file, Json::Value & root) { + Json::Reader reader; + String path = GetProfilePath(false); + std::ifstream in(path.ToCStr()); + if (!reader.parse(in, root)) { + // report to the user the failure and their locations in the document. + LogError("Failed to parse json file: %s\n %s", + file.ToCStr(), + reader.getFormattedErrorMessages().c_str()); + return false; + } + return true; + +} // Poplulates the local profile cache. This occurs on the first access of the profile // data. All profile operations are performed against the local cache until the // ProfileManager is released or goes out of scope at which time the cache is serialized // to disk. -void ProfileManager::LoadCache(ProfileType device) -{ - Lock::Locker lockScope(&ProfileLock); - - ClearCache(); - - String path = GetProfilePath(false); - - Ptr<JSON> root = *JSON::Load(path); - if (!root || root->GetItemCount() < 3) +void ProfileManager::LoadCache() { + if (Loaded) { + return; + } + + Lock::Locker lockScope(&ProfileLock); + ClearCache(); + + Json::Value root; + if (!parseJsonFile(GetProfilePath(false), root)) { + LogError("Failed to parse configuration"); + return; + } + + if (!root.isMember(KEY_PROFILE_VERSION)) { + LogError("Profile JSON is malformed, missing version number"); + return; + } + + int major = root[KEY_PROFILE_VERSION].asInt(); + switch (major) { + case 1: { + if (root.size() < 3) { + LogError("Profile JSON is malformed, insufficient keys"); return; - - // First read the file type and version to make sure this is a valid file - JSON* item0 = root->GetFirstItem(); - JSON* item1 = root->GetNextItem(item0); - JSON* item2 = root->GetNextItem(item1); - - if (item0->Name == "Oculus Profile Version") - { - int major = atoi(item0->Value.ToCStr()); - if (major > MAX_PROFILE_MAJOR_VERSION) - return; // don't parse the file on unsupported major version number - } - else - { + } + DefaultProfile = root[KEY_CURRENT_PROFILE].asCString(); + const Json::Value & profileNode = root["Profile"]; + String name = profileNode["Name"].asCString(); + // The profile having to know it's own name is a symptom of the lack + // of a proper associative array class in the OVR codebase. + Profile * profile = new Profile(name); + ProfileLoader::loadV1Profile(*profile, profileNode); + ProfileCache.PushBack(profile); + } + break; + + case 2: { + if (!root.isMember(KEY_PROFILES)) { + LogError("Missing profile data"); return; - } - - DefaultProfile = item1->Value; - - // Read the number of profiles - int profileCount = (int)item2->dValue; - JSON* profileItem = item2; - - for (int p=0; p<profileCount; p++) - { - profileItem = root->GetNextItem(profileItem); - if (profileItem == NULL) - break; - - if (profileItem->Name == "Profile") - { - // Read the required Name field - const char* profileName; - JSON* item = profileItem->GetFirstItem(); - - if (item && (item->Name == "Name")) - { - profileName = item->Value; - } - else - { - return; // invalid field - } - - const char* deviceName = 0; - bool deviceFound = false; - Ptr<Profile> profile = *CreateProfileObject(profileName, device, &deviceName); - - // Read the base profile fields. - if (profile) - { - while (item = profileItem->GetNextItem(item), item) - { - if (item->Type != JSON_Object) - { - profile->ParseProperty(item->Name, item->Value); - } - else - { // Search for the matching device to get device specific fields - if (!deviceFound && deviceName && OVR_strcmp(item->Name, deviceName) == 0) - { - deviceFound = true; - - for (JSON* deviceItem = item->GetFirstItem(); deviceItem; - deviceItem = item->GetNextItem(deviceItem)) - { - profile->ParseProperty(deviceItem->Name, deviceItem->Value); - } - } - } - } - } - - // Add the new profile - ProfileCache.PushBack(profile); - } - } - - CacheDevice = device; + } + + const Json::Value & profiles = root[KEY_PROFILES]; + Json::Value::const_iterator itr; + for (itr = profiles.begin(); itr != profiles.end(); ++itr) { + String profileName = itr.memberName(); + Profile * profile = new Profile(profileName); + ProfileLoader::loadProfile(*profile, root[profileName]); + ProfileCache.PushBack(profile); + } + + if (!root.isMember(KEY_CURRENT_PROFILE)) { + LogError("Missing current profile"); + } else { + DefaultProfile = root[KEY_CURRENT_PROFILE].asCString(); + } + } + break; + + default: + LogError("Usupported profile version %d", major); + return; // don't parse the file on unsupported major version number + } + Loaded = true; } - // Serializes the profiles to disk. void ProfileManager::SaveCache() { - String path = GetProfilePath(true); - Lock::Locker lockScope(&ProfileLock); - - Ptr<JSON> oldroot = *JSON::Load(path); - if (oldroot) + // Limit scope of reader + Json::Value root; + String path = GetProfilePath(false); + if (!parseJsonFile(path, root)) { - if (oldroot->GetItemCount() >= 3) - { - JSON* item0 = oldroot->GetFirstItem(); - JSON* item1 = oldroot->GetNextItem(item0); - oldroot->GetNextItem(item1); - - if (item0->Name == "Oculus Profile Version") - { - int major = atoi(item0->Value.ToCStr()); - if (major > MAX_PROFILE_MAJOR_VERSION) - oldroot.Clear(); // don't use the file on unsupported major version number - } - else - { - oldroot.Clear(); - } - } - else - { - oldroot.Clear(); - } + root = Json::Value(); } - - // Create a new json root - Ptr<JSON> root = *JSON::CreateObject(); - root->AddNumberItem("Oculus Profile Version", PROFILE_VERSION); - root->AddStringItem("CurrentProfile", DefaultProfile); - root->AddNumberItem("ProfileCount", (double) ProfileCache.GetSize()); - - // Generate a JSON subtree for each profile - for (unsigned int i=0; i<ProfileCache.GetSize(); i++) - { - Profile* profile = ProfileCache[i]; - - // Write the base profile information - JSON* json_profile = JSON::CreateObject(); - json_profile->Name = "Profile"; - json_profile->AddStringItem("Name", profile->Name); - const char* gender; - switch (profile->GetGender()) - { - case Profile::Gender_Male: gender = "Male"; break; - case Profile::Gender_Female: gender = "Female"; break; - default: gender = "Unspecified"; - } - json_profile->AddStringItem("Gender", gender); - json_profile->AddNumberItem("PlayerHeight", profile->PlayerHeight); - json_profile->AddNumberItem("IPD", profile->IPD); - - char* device_name = NULL; - // Create a device-specific subtree for the cached device - if (profile->Type == Profile_RiftDK1) - { - device_name = "RiftDK1"; - - RiftDK1Profile* rift = (RiftDK1Profile*)profile; - JSON* json_rift = JSON::CreateObject(); - json_profile->AddItem(device_name, json_rift); - - const char* eyecup = "A"; - switch (rift->EyeCups) - { - case EyeCup_A: eyecup = "A"; break; - case EyeCup_B: eyecup = "B"; break; - case EyeCup_C: eyecup = "C"; break; - } - json_rift->AddStringItem("EyeCup", eyecup); - json_rift->AddNumberItem("LL", rift->LL); - json_rift->AddNumberItem("LR", rift->LR); - json_rift->AddNumberItem("RL", rift->RL); - json_rift->AddNumberItem("RR", rift->RR); - } - else if (profile->Type == Profile_RiftDKHD) - { - device_name = "RiftDKHD"; - - RiftDKHDProfile* rift = (RiftDKHDProfile*)profile; - JSON* json_rift = JSON::CreateObject(); - json_profile->AddItem(device_name, json_rift); - - const char* eyecup = "A"; - switch (rift->EyeCups) - { - case EyeCup_A: eyecup = "A"; break; - case EyeCup_B: eyecup = "B"; break; - case EyeCup_C: eyecup = "C"; break; - } - json_rift->AddStringItem("EyeCup", eyecup); - //json_rift->AddNumberItem("LL", rift->LL); - //json_rift->AddNumberItem("LR", rift->LR); - //json_rift->AddNumberItem("RL", rift->RL); - //json_rift->AddNumberItem("RR", rift->RR); - } - - // There may be multiple devices stored per user, but only a single - // device is represented by this root. We don't want to overwrite - // the other devices so we need to examine the older root - // and merge previous devices into new json root - if (oldroot) - { - JSON* old_profile = oldroot->GetFirstItem(); - while (old_profile) - { - if (old_profile->Name == "Profile") - { - JSON* profile_name = old_profile->GetItemByName("Name"); - if (profile_name && OVR_strcmp(profile->Name, profile_name->Value) == 0) - { // Now that we found the user in the older root, add all the - // object children to the new root - except for the one for the - // current device - JSON* old_item = old_profile->GetFirstItem(); - while (old_item) - { - if (old_item->Type == JSON_Object - && (device_name == NULL || OVR_strcmp(old_item->Name, device_name) != 0)) - { - JSON* old_device = old_item; - old_item = old_profile->GetNextItem(old_item); - - // remove the node from the older root to avoid multiple reference - old_device->RemoveNode(); - // add the node pointer to the new root - json_profile->AddItem(old_device->Name, old_device); - } - else - { - old_item = old_profile->GetNextItem(old_item); - } - } - - break; - } - } - - old_profile = oldroot->GetNextItem(old_profile); - } - } - // Add the completed user profile to the new root - root->AddItem("Profile", json_profile); + // If the file is V1, we don't want to preserve it. + // Technically, we could make an effort to preserve it but explcitly + // remove the V1 tokens we don't use in V2, but that's a bunch of + // effort that will likely serve no useful purpose. Since the file + // isn't valid JSON if it has multiple profiles, I doubt anyone is + // extending it yet + if (root.isMember(KEY_PROFILE_VERSION) && + PROFILE_VERSION != root[KEY_PROFILE_VERSION].asInt()) { + root = Json::Value(); } - // Save the profile to disk - root->Save(path); -} - -// Returns the number of stored profiles for this device type -int ProfileManager::GetProfileCount(ProfileType device) -{ - Lock::Locker lockScope(&ProfileLock); - - if (CacheDevice == Profile_Unknown) - LoadCache(device); - - return (int)ProfileCache.GetSize(); -} - -// Returns the profile name of a specific profile in the list. The returned -// memory is locally allocated and should not be stored or deleted. Returns NULL -// if the index is invalid -const char* ProfileManager::GetProfileName(ProfileType device, unsigned int index) -{ - Lock::Locker lockScope(&ProfileLock); - - if (CacheDevice == Profile_Unknown) - LoadCache(device); + root[KEY_PROFILE_VERSION] = PROFILE_VERSION; + if (!DefaultProfile.IsEmpty()) { + root[KEY_CURRENT_PROFILE] = DefaultProfile.ToCStr(); + } - if (index < ProfileCache.GetSize()) - { - Profile* profile = ProfileCache[index]; - OVR_strcpy(NameBuff, Profile::MaxNameLen, profile->Name); - return NameBuff; + // Generate a JSON object of 'profile name' to 'profile data' + Json::Value & profiles = root[KEY_PROFILES]; + for (unsigned int i=0; i<ProfileCache.GetSize(); i++) { + const Profile * profile = ProfileCache[i]; + ProfileLoader::writeProfile(profiles, *profile); } - else + { - return NULL; + Json::StyledWriter writer; + std::string output = writer.write( root ); + SysFile f; + if (!f.Open(path, File::Open_Write | File::Open_Create | File::Open_Truncate, File::Mode_Write)) { + LogError("Unable to open %s for writing", path.ToCStr()); + return; + } + int written = f.Write((const unsigned char*)output.c_str(), + output.length()); + if (written != output.length()) { + LogError("Short write, only %d of %d bytes written", + written, (int)output.length()); + } + f.Close(); } } -bool ProfileManager::HasProfile(ProfileType device, const char* name) +// Returns the number of stored profiles for this device type +unsigned int ProfileManager::GetProfileCount() { Lock::Locker lockScope(&ProfileLock); - - if (CacheDevice == Profile_Unknown) - LoadCache(device); - - for (unsigned i = 0; i< ProfileCache.GetSize(); i++) - { - if (ProfileCache[i] && OVR_strcmp(ProfileCache[i]->Name, name) == 0) - return true; - } - return false; + return ProfileCache.GetSize(); } - -// Returns a specific profile object in the list. The returned memory should be -// encapsulated in a Ptr<> object or released after use. Returns NULL if the index -// is invalid -Profile* ProfileManager::LoadProfile(ProfileType device, unsigned int index) +bool ProfileManager::HasProfile(const char* name) { - Lock::Locker lockScope(&ProfileLock); - - if (CacheDevice == Profile_Unknown) - LoadCache(device); - - if (index < ProfileCache.GetSize()) - { - Profile* profile = ProfileCache[index]; - return profile->Clone(); - } - else - { - return NULL; - } + return NULL != ProfileCache.at(name); } -// Returns a profile object for a particular device and user name. The returned -// memory should be encapsulated in a Ptr<> object or released after use. Returns +// Returns a profile object for a particular device and user name. The returned +// memory should be encapsulated in a Ptr<> object or released after use. Returns // NULL if the profile is not found -Profile* ProfileManager::LoadProfile(ProfileType device, const char* user) +Profile* ProfileManager::LoadProfile(const char* user) { + // Maybe 'null' should be interpreted as 'return the default?' if (user == NULL) return NULL; Lock::Locker lockScope(&ProfileLock); - - if (CacheDevice == Profile_Unknown) - LoadCache(device); - - for (unsigned int i=0; i<ProfileCache.GetSize(); i++) - { - if (OVR_strcmp(user, ProfileCache[i]->Name) == 0) - { // Found the requested user profile - Profile* profile = ProfileCache[i]; - return profile->Clone(); - } + LoadCache(); + Profile * result = ProfileCache.at(user); + if (!result) { + return NULL; } - - return NULL; + // Never give the caller memory that we ourselves are managing. + return result->Clone(); } // Returns a profile with all system default values -Profile* ProfileManager::GetDeviceDefaultProfile(ProfileType device) +Profile* ProfileManager::GetDefaultProfile() { - const char* device_name = NULL; - return CreateProfileObject("default", device, &device_name); + return new Profile("default"); } // Returns the name of the profile that is marked as the current default user. -const char* ProfileManager::GetDefaultProfileName(ProfileType device) +const char* ProfileManager::GetDefaultProfileName() { Lock::Locker lockScope(&ProfileLock); - - if (CacheDevice == Profile_Unknown) - LoadCache(device); + LoadCache(); if (ProfileCache.GetSize() > 0) { - OVR_strcpy(NameBuff, Profile::MaxNameLen, DefaultProfile); + OVR_strcpy(NameBuff, 32, DefaultProfile); return NameBuff; } else @@ -558,14 +607,12 @@ const char* ProfileManager::GetDefaultProfileName(ProfileType device) } } + // Marks a particular user as the current default user. -bool ProfileManager::SetDefaultProfileName(ProfileType device, const char* name) -{ +bool ProfileManager::SetDefaultProfileName(const char* name) { Lock::Locker lockScope(&ProfileLock); - - if (CacheDevice == Profile_Unknown) - LoadCache(device); -// TODO: I should verify that the user is valid + LoadCache(); + // TODO: I should verify that the user is valid if (ProfileCache.GetSize() > 0) { DefaultProfile = name; @@ -581,132 +628,67 @@ bool ProfileManager::SetDefaultProfileName(ProfileType device, const char* name) // Saves a new or existing profile. Returns true on success or false on an // invalid or failed save. -bool ProfileManager::Save(const Profile* profile) +bool ProfileManager::Save(const Profile * profile) { - Lock::Locker lockScope(&ProfileLock); + if (NULL == profile) { + return false; + } if (OVR_strcmp(profile->Name, "default") == 0) return false; // don't save a default profile - // TODO: I should also verify that this profile type matches the current cache - if (CacheDevice == Profile_Unknown) - LoadCache(profile->Type); + Lock::Locker lockScope(&ProfileLock); + LoadCache(); // Look for the pre-existence of this profile - bool added = false; - for (unsigned int i=0; i<ProfileCache.GetSize(); i++) - { - int compare = OVR_strcmp(profile->Name, ProfileCache[i]->Name); - - if (compare == 0) - { - // TODO: I should do a proper field comparison to avoid unnecessary - // overwrites and file saves - - // Replace the previous instance with the new profile - ProfileCache[i] = *profile->Clone(); - added = true; - Changed = true; - break; - } - } - - if (!added) - { - ProfileCache.PushBack(*profile->Clone()); - if (ProfileCache.GetSize() == 1) - CacheDevice = profile->Type; + int index = ProfileCache.IndexOf(profile->Name); + if (Map::npos == index) { + // TODO: I should do a proper field comparison to avoid unnecessary + // overwrites and file saves - Changed = true; + // Replace the previous instance with the new profile + ProfileCache[index] = profile->Clone(); + } else { + ProfileCache.PushBack(profile->Clone()); } - + Changed = true; return true; } // Removes an existing profile. Returns true if the profile was found and deleted // and returns false otherwise. -bool ProfileManager::Delete(const Profile* profile) +bool ProfileManager::Delete(const Profile * profile) { + if (NULL == profile) { + return false; + } Lock::Locker lockScope(&ProfileLock); - - if (OVR_strcmp(profile->Name, "default") == 0) - return false; // don't delete a default profile - - if (CacheDevice == Profile_Unknown) - LoadCache(profile->Type); - - // Look for the existence of this profile - for (unsigned int i=0; i<ProfileCache.GetSize(); i++) - { - if (OVR_strcmp(profile->Name, ProfileCache[i]->Name) == 0) - { - if (OVR_strcmp(profile->Name, DefaultProfile) == 0) - DefaultProfile.Clear(); - - ProfileCache.RemoveAt(i); - Changed = true; - return true; - } + int index = ProfileCache.IndexOf(profile->Name); + if (Map::npos == index) { + return false; } - - return false; + ProfileCache.RemoveAt(index); + return true; } - - //----------------------------------------------------------------------------- // ***** Profile -Profile::Profile(ProfileType device, const char* name) +Profile::Profile(const char* name) { - Type = device; Gender = Gender_Unspecified; - PlayerHeight = 1.778f; // 5'10" inch man - IPD = 0.064f; - OVR_strcpy(Name, MaxNameLen, name); -} - - -bool Profile::ParseProperty(const char* prop, const char* sval) -{ - if (OVR_strcmp(prop, "Name") == 0) - { - OVR_strcpy(Name, MaxNameLen, sval); - return true; - } - else if (OVR_strcmp(prop, "Gender") == 0) - { - if (OVR_strcmp(sval, "Male") == 0) - Gender = Gender_Male; - else if (OVR_strcmp(sval, "Female") == 0) - Gender = Gender_Female; - else - Gender = Gender_Unspecified; - - return true; - } - else if (OVR_strcmp(prop, "PlayerHeight") == 0) - { - PlayerHeight = (float)atof(sval); - return true; - } - else if (OVR_strcmp(prop, "IPD") == 0) - { - IPD = (float)atof(sval); - return true; - } - - return false; + PlayerHeight = DEFAULT_HEIGHT; + IPD = DEFAULT_IPD; + Name = name; } - // Computes the eye height from the metric head height -float Profile::GetEyeHeight() +float Profile::GetEyeHeight() const { const float EYE_TO_HEADTOP_RATIO = 0.44538f; const float MALE_AVG_HEAD_HEIGHT = 0.232f; const float FEMALE_AVG_HEAD_HEIGHT = 0.218f; - + // compute distance from top of skull to the eye float head_height; if (Gender == Gender_Female) @@ -720,107 +702,4 @@ float Profile::GetEyeHeight() return eye_height; } -//----------------------------------------------------------------------------- -// ***** HMDProfile - -HMDProfile::HMDProfile(ProfileType type, const char* name) : Profile(type, name) -{ - LL = 0; - LR = 0; - RL = 0; - RR = 0; -} - -bool HMDProfile::ParseProperty(const char* prop, const char* sval) -{ - if (OVR_strcmp(prop, "LL") == 0) - { - LL = atoi(sval); - return true; - } - else if (OVR_strcmp(prop, "LR") == 0) - { - LR = atoi(sval); - return true; - } - else if (OVR_strcmp(prop, "RL") == 0) - { - RL = atoi(sval); - return true; - } - else if (OVR_strcmp(prop, "RR") == 0) - { - RR = atoi(sval); - return true; - } - - return Profile::ParseProperty(prop, sval); -} - -Profile* HMDProfile::Clone() const -{ - HMDProfile* profile = new HMDProfile(*this); - return profile; -} - -//----------------------------------------------------------------------------- -// ***** RiftDK1Profile - -RiftDK1Profile::RiftDK1Profile(const char* name) : HMDProfile(Profile_RiftDK1, name) -{ - EyeCups = EyeCup_A; -} - -bool RiftDK1Profile::ParseProperty(const char* prop, const char* sval) -{ - if (OVR_strcmp(prop, "EyeCup") == 0) - { - switch (sval[0]) - { - case 'C': EyeCups = EyeCup_C; break; - case 'B': EyeCups = EyeCup_B; break; - default: EyeCups = EyeCup_A; break; - } - return true; - } - - return HMDProfile::ParseProperty(prop, sval); -} - -Profile* RiftDK1Profile::Clone() const -{ - RiftDK1Profile* profile = new RiftDK1Profile(*this); - return profile; -} - -//----------------------------------------------------------------------------- -// ***** RiftDKHDProfile - -RiftDKHDProfile::RiftDKHDProfile(const char* name) : HMDProfile(Profile_RiftDKHD, name) -{ - EyeCups = EyeCup_A; -} - -bool RiftDKHDProfile::ParseProperty(const char* prop, const char* sval) -{ - if (OVR_strcmp(prop, "EyeCup") == 0) - { - switch (sval[0]) - { - case 'C': EyeCups = EyeCup_C; break; - case 'B': EyeCups = EyeCup_B; break; - default: EyeCups = EyeCup_A; break; - } - return true; - } - - return HMDProfile::ParseProperty(prop, sval); -} - -Profile* RiftDKHDProfile::Clone() const -{ - RiftDKHDProfile* profile = new RiftDKHDProfile(*this); - return profile; -} - } // OVR diff --git a/LibOVR/Src/OVR_Profile.h b/LibOVR/Src/OVR_Profile.h index df25fea..2c5e4c2 100644 --- a/LibOVR/Src/OVR_Profile.h +++ b/LibOVR/Src/OVR_Profile.h @@ -6,7 +6,7 @@ Content : Structs and functions for loading and storing device profile set Created : February 14, 2013 Notes : Profiles are used to store per-user settings that can be transferred and used - across multiple applications. For example, player IPD can be configured once + across multiple applications. For example, player IPD can be configured once and reused for a unified experience across games. Configuration and saving of profiles can be accomplished in game via the Profile API or by the official Oculus Configuration Utility. @@ -25,11 +25,12 @@ otherwise accompanies this software in either electronic or hard copy form. #include "Kernel/OVR_String.h" #include "Kernel/OVR_RefCount.h" #include "Kernel/OVR_Array.h" +#include "Kernel/OVR_Math.h" namespace OVR { // Defines the profile object for each device type -enum ProfileType +enum ProfileDeviceType { Profile_Unknown = 0, Profile_GenericHMD = 10, @@ -37,119 +38,17 @@ enum ProfileType Profile_RiftDKHD = 12, }; -class Profile; - -// ----------------------------------------------------------------------------- -// ***** ProfileManager - -// Profiles are interfaced through a ProfileManager object. Applications should -// create a ProfileManager each time they intend to read or write user profile data. -// The scope of the ProfileManager object defines when disk I/O is performed. Disk -// reads are performed on the first profile access and disk writes are performed when -// the ProfileManager goes out of scope. All profile interactions between these times -// are performed in local memory and are fast. A typical profile interaction might -// look like this: // -// { -// Ptr<ProfileManager> pm = *ProfileManager::Create(); -// Ptr<Profile> profile = pm->LoadProfile(Profile_RiftDK1, -// pm->GetDefaultProfileName(Profile_RiftDK1)); -// if (profile) -// { // Retrieve the current profile settings -// } -// } // Profile will be destroyed and any disk I/O completed when going out of scope - -class ProfileManager : public RefCountBase<ProfileManager> -{ -protected: - // Synchronize ProfileManager access since it may be accessed from multiple threads, - // as it's shared through DeviceManager. - Lock ProfileLock; - Array<Ptr<Profile> > ProfileCache; - ProfileType CacheDevice; - String DefaultProfile; - bool Changed; - char NameBuff[32]; - -public: - static ProfileManager* Create(); - - // Static interface functions - int GetProfileCount(ProfileType device); - const char* GetProfileName(ProfileType device, unsigned int index); - bool HasProfile(ProfileType device, const char* name); - Profile* LoadProfile(ProfileType device, unsigned int index); - Profile* LoadProfile(ProfileType device, const char* name); - Profile* GetDeviceDefaultProfile(ProfileType device); - const char* GetDefaultProfileName(ProfileType device); - bool SetDefaultProfileName(ProfileType device, const char* name); - bool Save(const Profile* profile); - bool Delete(const Profile* profile); - -protected: - ProfileManager(); - ~ProfileManager(); - void LoadCache(ProfileType device); - void SaveCache(); - void ClearCache(); - Profile* CreateProfileObject(const char* user, - ProfileType device, - const char** device_name); -}; - -//------------------------------------------------------------------- -// ***** Profile - -// The base profile for all users. This object is not created directly. -// Instead derived device objects provide add specific device members to -// the base profile - -class Profile : public RefCountBase<Profile> -{ -public: - enum { MaxNameLen = 32 }; - - enum GenderType - { - Gender_Unspecified = 0, - Gender_Male = 1, - Gender_Female = 2 - }; - - ProfileType Type; // The type of device profile - char Name[MaxNameLen]; // The name given to this profile - -protected: - GenderType Gender; // The gender of the user - float PlayerHeight; // The height of the user in meters - float IPD; // Distance between eyes in meters - -public: - virtual Profile* Clone() const = 0; - - // These are properties which are intrinsic to the user and affect scene setup - GenderType GetGender() { return Gender; }; - float GetPlayerHeight() { return PlayerHeight; }; - float GetIPD() { return IPD; }; - float GetEyeHeight(); - - void SetGender(GenderType gender) { Gender = gender; }; - void SetPlayerHeight(float height) { PlayerHeight = height; }; - void SetIPD(float ipd) { IPD = ipd; }; - -protected: - Profile(ProfileType type, const char* name); - - virtual bool ParseProperty(const char* prop, const char* sval); - - friend class ProfileManager; -}; +// HMDProfile and it's child classes, RiftProfile, RiftDk1Profile and +// RiftDKHDProfile represent the intersection of 'per-user' and 'per-device' +// settings. +// //----------------------------------------------------------------------------- // ***** HMDProfile // The generic HMD profile is used for properties that are common to all headsets -class HMDProfile : public Profile +class HmdDevice { protected: // FOV extents in pixels measured by a user @@ -159,8 +58,7 @@ protected: int RR; // right eye outer extent public: - virtual Profile* Clone() const; - + virtual ~HmdDevice() {} void SetLL(int val) { LL = val; }; void SetLR(int val) { LR = val; }; void SetRL(int val) { RL = val; }; @@ -170,13 +68,19 @@ public: int GetLR() { return LR; }; int GetRL() { return RL; }; int GetRR() { return RR; }; + virtual ProfileDeviceType GetDeviceType() const { + return Profile_GenericHMD; + } protected: - HMDProfile(ProfileType type, const char* name); - - virtual bool ParseProperty(const char* prop, const char* sval); - - friend class ProfileManager; + HmdDevice() { + LL = 0; + LR = 0; + RL = 0; + RR = 0; + } + friend class ProfileLoader; + friend class Profile; }; // For headsets that use eye cups @@ -187,52 +91,259 @@ enum EyeCupType EyeCup_C = 2 }; -//----------------------------------------------------------------------------- -// ***** RiftDK1Profile - -// This profile is specific to the Rift Dev Kit 1 and contains overrides specific -// to that device and lens cup settings. -class RiftDK1Profile : public HMDProfile -{ +class RiftDevice : public HmdDevice { protected: EyeCupType EyeCups; // Which eye cup does the player use public: - virtual Profile* Clone() const; - EyeCupType GetEyeCup() { return EyeCups; }; void SetEyeCup(EyeCupType cup) { EyeCups = cup; }; protected: - RiftDK1Profile(const char* name); + RiftDevice() { + EyeCups = EyeCup_A; + } + friend class ProfileLoader; + friend class Profile; +}; - virtual bool ParseProperty(const char* prop, const char* sval); +//----------------------------------------------------------------------------- +// ***** RiftDK1Profile - friend class ProfileManager; +// This profile is specific to the Rift Dev Kit 1 and contains overrides specific +// to that device and lens cup settings. +class RiftDK1Device : public RiftDevice +{ +public: + virtual ProfileDeviceType GetDeviceType() const { + return Profile_RiftDK1; + } + +protected: + RiftDK1Device() { + + } + friend class ProfileLoader; + friend class Profile; }; //----------------------------------------------------------------------------- // ***** RiftDKHDProfile -// This profile is specific to the Rift HD Dev Kit and contains overrides specific +// This profile is specific to the Rift HD Dev Kit and contains overrides specific // to that device and lens cup settings. -class RiftDKHDProfile : public HMDProfile +class RiftDKHDDevice : public RiftDevice { +public: + virtual ProfileDeviceType GetDeviceType() const { + return Profile_RiftDKHD; + } + protected: - EyeCupType EyeCups; // Which eye cup does the player use + RiftDKHDDevice() { + } + friend class ProfileLoader; + friend class Profile; +}; +template <class KeyType> +class KeyedObject { public: - virtual Profile* Clone() const; + virtual ~KeyedObject() { + } + virtual const KeyType & GetKey() const = 0; +}; +//------------------------------------------------------------------- +// ***** Profile - EyeCupType GetEyeCup() { return EyeCups; }; - void SetEyeCup(EyeCupType cup) { EyeCups = cup; }; +// The base profile for all users. This object is not created directly. +// +class Profile : public RefCountBase<Profile>, KeyedObject<String> +{ +public: + enum GenderType + { + Gender_Unspecified = 0, + Gender_Male = 1, + Gender_Female = 2 + }; + + String Name; // The name given to this profile protected: - RiftDKHDProfile(const char* name); + GenderType Gender; // The gender of the user + float PlayerHeight; // The height of the user in meters + float IPD; // Distance between eyes in meters + Quatf StrabismusCorrection; // Amount to rotate modelview matrix to correct for corss-eyed vision + // Should be applied as is to the left eye, and inverted to apply to the + // right eye + HmdDevice Generic; + RiftDK1Device RiftDK1; + RiftDKHDDevice RiftDKHD; - virtual bool ParseProperty(const char* prop, const char* sval); +public: + const String & GetKey() const { return Name; }; + // These are properties which are intrinsic to the user and affect scene setup + GenderType GetGender() const { return Gender; }; + float GetPlayerHeight() const { return PlayerHeight; }; + float GetIPD() const { return IPD; }; + float GetEyeHeight() const; + const Quatf & GetStrabismusCorrection() const { return StrabismusCorrection; }; + HmdDevice & GetGenericDevice() { return Generic; } + RiftDK1Device & GetRiftDK1Device() { return RiftDK1; } + RiftDKHDDevice & GetRiftDKHDDevice() { return RiftDKHD; } + const HmdDevice & GetGenericDevice() const { return Generic; } + const RiftDK1Device& GetRiftDK1Device() const { return RiftDK1; } + const RiftDKHDDevice&GetRiftDKHDDevice() const { return RiftDKHD; } + + void SetGender(GenderType gender) { Gender = gender; }; + void SetPlayerHeight(float height) { PlayerHeight = height; }; + void SetIPD(float ipd) { IPD = ipd; }; + void SetStrabismusCorrection(const Quatf & quat) { StrabismusCorrection = quat; }; + Profile * Clone() const { + return new Profile(*this); + } +protected: + Profile(const char* name); friend class ProfileManager; + friend class ProfileLoader; +}; + + +/* + * A really hacky low-performing associative array, because the + * ProfileManager is attempting to mimic the functionality of one + * + * It should have the same performance characteristics as the + * previous ProfileManager embedded implementation. I'm not write a + * full treemap or hashmap implementaiton because, seriously, there are + * plenty out there in the standard libraries, and if OVR doesn't want to + * use them then I'm not going to get roped into building one just to + * support the policy of never using STL. + * + * Using at(KeyType) instead of operator[KeyType] since the latter would + * interfere with ability to access the index based base class operator[] + */ +template <class ValueType, class KeyType> +class AssociativePtrArray : public Array<Ptr<ValueType> > { +public: + enum { + npos = -1 + }; + + int IndexOf(const KeyType & key) const { + for (int i = 0; i< this->GetSize(); ++i) { + const Ptr<ValueType> & data = this->Data.Data[i]; + if (data && data->GetKey() == key) { + return i; + } + } + return -1; + } + + ValueType * at(const KeyType & key) { + int index = IndexOf(key); + if (npos == index) { + return Ptr<ValueType>(); + } + Ptr<ValueType> & ptr = this->Data.Data[index]; + return ptr; + } + + const ValueType * at(const KeyType & key) const { + int index = IndexOf(key); + if (npos == index) { + return Ptr<ValueType>(); + } + const Ptr<ValueType> & ptr = this->Data.Data[index]; + return ptr; + } +}; + +// ----------------------------------------------------------------------------- +// ***** ProfileManager + +// Profiles are interfaced through a ProfileManager object. Applications should +// create a ProfileManager each time they intend to read or write user profile data. +// The scope of the ProfileManager object defines when disk I/O is performed. Disk +// reads are performed on the first profile access and disk writes are performed when +// the ProfileManager goes out of scope. All profile interactions between these times +// are performed in local memory and are fast. A typical profile interaction might +// look like this: +// +// { +// Ptr<ProfileManager> pm = *ProfileManager::Create(); +// Ptr<Profile> profile = pm->LoadProfile(pm->GetDefaultProfileName()); +// if (profile) +// { // Retrieve the current profile settings +// } +// } // Profile will be destroyed and any disk I/O completed when going out of scope + +class ProfileManager : public RefCountBase<ProfileManager> +{ +protected: + // Synchronize ProfileManager access since it may be accessed from multiple threads, + // as it's shared through DeviceManager. + Lock ProfileLock; + typedef AssociativePtrArray<Profile, String> Map; + Map ProfileCache; + String DefaultProfile; + bool Changed; + bool Loaded; + // A container for the name data returned by GetDefaultProfileName() + // which can't be a String, because once acquired by the caller, + // it can't be allowed to be deallocated without risk. Ultimately + // this is caused by the inability to be able to safely return the + // String type from methods, which itself boils down to the problem + // with providing an explicit operator const char *() on a String + // class. Doing so makes it too easy for a caller to get a char * + // to a temporary or have the pointer become deallocated memory + // at some later point unexpectedly. + char NameBuff[32]; + +public: + static ProfileManager* Create(); + + // Static interface functions + unsigned GetProfileCount(); + bool HasProfile(const char* name); + Profile* LoadProfile(const char* name); + Profile* GetDefaultProfile(); + const char* GetDefaultProfileName(); + bool SetDefaultProfileName(const char* name); + + // The previous implementation seemed to imply that if you loaded + // some profiles for a given 'device type' and then saved changes, + // and then loaded a different device type, your changes would be lost + // because the cache would be cleared without ever being persisted to + // disk. The redesign fixes this by eliminating the 'per-device-type' + // profile mechanism. + // + // Profiles represent a users settings and they + // may have different settings for each device, so the profile should + // encapsulate all of them. + // + // Ideally, you should be able to query the top level profile for + // properties related to the hardware based on whatever you're using, + // rather than having to query specifically for generic, DK1 or DKHD + // but the class hierarchy makes that problematic. + bool Save(const Profile * profile); + bool Delete(const Profile * profile); + + // Index based fetching is completely removed. I have no idea what + // was intended to be useful for. Perhaps it was intended as a + // potential optimization since querying by name is O(N) instead of + // O(1), but it's unfathomable that the performance difference would + // ever be noticeable, unless the caller is doing something pathological + // like calling using profile manager every frame, and unlikely even + // then +protected: + ProfileManager(); + ~ProfileManager(); + void LoadCache(); + void SaveCache(); + void ClearCache(); }; @@ -240,4 +351,4 @@ String GetBaseOVRPath(bool create_dir); } -#endif // OVR_Profile_h
\ No newline at end of file +#endif // OVR_Profile_h |