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, 716 insertions, 705 deletions
diff --git a/LibOVR/Src/OVR_Linux_HMDDevice.cpp b/LibOVR/Src/OVR_Linux_HMDDevice.cpp index 52f537d..cce37d5 100644 --- a/LibOVR/Src/OVR_Linux_HMDDevice.cpp +++ b/LibOVR/Src/OVR_Linux_HMDDevice.cpp @@ -234,13 +234,14 @@ 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(); + profileManager->GetDefaultProfileName(profileType); return profileName ? - profileManager->LoadProfile(profileName) : - profileManager->GetDefaultProfile(); + profileManager->LoadProfile(profileType, profileName) : + profileManager->GetDeviceDefaultProfile(profileType); } @@ -340,7 +341,7 @@ bool HMDDevice::Initialize(DeviceBase* parent) // Initialize user profile to default for device. ProfileManager* profileManager = GetManager()->GetProfileManager(); - ProfileName = profileManager->GetDefaultProfileName(); + ProfileName = profileManager->GetDefaultProfileName(getDesc()->GetProfileType()); return true; } @@ -371,7 +372,7 @@ bool HMDDevice::SetProfileName(const char* name) ProfileName.Clear(); return 0; } - if (GetManager()->GetProfileManager()->HasProfile(name)) + if (GetManager()->GetProfileManager()->HasProfile(getDesc()->GetProfileType(), name)) { ProfileName = name; return true; diff --git a/LibOVR/Src/OVR_Linux_HMDDevice.h b/LibOVR/Src/OVR_Linux_HMDDevice.h index 2350958..d0585d1 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; - ProfileDeviceType GetProfileType() const + ProfileType 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 bf8906e..3be43db 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,13 +21,12 @@ otherwise accompanies this software in either electronic or hard copy form. ************************************************************************************/ #include "OVR_Profile.h" -#include "OVR_Log.h" +#include "OVR_JSON.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 @@ -41,33 +40,8 @@ otherwise accompanies this software in either electronic or hard copy form. #endif - -#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 - +#define PROFILE_VERSION 1.0 +#define MAX_PROFILE_MAJOR_VERSION 1 namespace OVR { @@ -82,7 +56,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) @@ -93,11 +67,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"); @@ -118,21 +92,11 @@ String GetBaseOVRPath(bool create_dir) } #else - // 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"; + + passwd* pwd = getpwuid(getuid()); + const char* home = pwd->pw_dir; + path = home; + path += "/.config/Oculus"; if (create_dir) { // Create the Oculus directory if it doesn't exist @@ -165,7 +129,7 @@ String GetProfilePath(bool create_dir) ProfileManager::ProfileManager() { Changed = false; - Loaded = false; + CacheDevice = Profile_Unknown; } ProfileManager::~ProfileManager() @@ -182,423 +146,410 @@ 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(); - Loaded = false; + CacheDevice = Profile_Unknown; } -// 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() { - 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"); +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) return; - } - 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"); + + // 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 + { return; - } - - 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; + } + + 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; } + // Serializes the profiles to disk. void ProfileManager::SaveCache() { + String path = GetProfilePath(true); + Lock::Locker lockScope(&ProfileLock); - // Limit scope of reader - Json::Value root; - String path = GetProfilePath(false); - if (!parseJsonFile(path, root)) + + Ptr<JSON> oldroot = *JSON::Load(path); + if (oldroot) { - root = Json::Value(); + 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(); + } } + + // 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()); - // 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(); - } + // 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); - root[KEY_PROFILE_VERSION] = PROFILE_VERSION; - if (!DefaultProfile.IsEmpty()) { - root[KEY_CURRENT_PROFILE] = DefaultProfile.ToCStr(); - } + 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); + } + } - // 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); + // Add the completed user profile to the new root + root->AddItem("Profile", json_profile); } + // 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); + + if (index < ProfileCache.GetSize()) { - 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(); + Profile* profile = ProfileCache[index]; + OVR_strcpy(NameBuff, Profile::MaxNameLen, profile->Name); + return NameBuff; + } + else + { + return NULL; } } -// Returns the number of stored profiles for this device type -unsigned int ProfileManager::GetProfileCount() +bool ProfileManager::HasProfile(ProfileType device, const char* name) { Lock::Locker lockScope(&ProfileLock); - return ProfileCache.GetSize(); + + 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; } -bool ProfileManager::HasProfile(const char* name) + +// 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) { - return NULL != ProfileCache.at(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; + } } -// 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(const char* user) +Profile* ProfileManager::LoadProfile(ProfileType device, const char* user) { - // Maybe 'null' should be interpreted as 'return the default?' if (user == NULL) return NULL; Lock::Locker lockScope(&ProfileLock); - LoadCache(); - Profile * result = ProfileCache.at(user); - if (!result) { - return NULL; + + 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(); + } } - // Never give the caller memory that we ourselves are managing. - return result->Clone(); + + return NULL; } // Returns a profile with all system default values -Profile* ProfileManager::GetDefaultProfile() +Profile* ProfileManager::GetDeviceDefaultProfile(ProfileType device) { - return new Profile("default"); + const char* device_name = NULL; + return CreateProfileObject("default", device, &device_name); } // Returns the name of the profile that is marked as the current default user. -const char* ProfileManager::GetDefaultProfileName() +const char* ProfileManager::GetDefaultProfileName(ProfileType device) { Lock::Locker lockScope(&ProfileLock); - LoadCache(); + + if (CacheDevice == Profile_Unknown) + LoadCache(device); if (ProfileCache.GetSize() > 0) { - OVR_strcpy(NameBuff, 32, DefaultProfile); + OVR_strcpy(NameBuff, Profile::MaxNameLen, DefaultProfile); return NameBuff; } else @@ -607,12 +558,14 @@ const char* ProfileManager::GetDefaultProfileName() } } - // Marks a particular user as the current default user. -bool ProfileManager::SetDefaultProfileName(const char* name) { +bool ProfileManager::SetDefaultProfileName(ProfileType device, const char* name) +{ Lock::Locker lockScope(&ProfileLock); - LoadCache(); - // TODO: I should verify that the user is valid + + if (CacheDevice == Profile_Unknown) + LoadCache(device); +// TODO: I should verify that the user is valid if (ProfileCache.GetSize() > 0) { DefaultProfile = name; @@ -628,67 +581,132 @@ bool ProfileManager::SetDefaultProfileName(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) { - if (NULL == profile) { - return false; - } + Lock::Locker lockScope(&ProfileLock); if (OVR_strcmp(profile->Name, "default") == 0) return false; // don't save a default profile - Lock::Locker lockScope(&ProfileLock); - LoadCache(); + // TODO: I should also verify that this profile type matches the current cache + if (CacheDevice == Profile_Unknown) + LoadCache(profile->Type); // Look for the pre-existence of this profile - 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 + 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; - // Replace the previous instance with the new profile - ProfileCache[index] = profile->Clone(); - } else { - ProfileCache.PushBack(profile->Clone()); + Changed = true; } - 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); - int index = ProfileCache.IndexOf(profile->Name); - if (Map::npos == index) { - return false; + + 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; + } } - ProfileCache.RemoveAt(index); - return true; + + return false; } + + //----------------------------------------------------------------------------- // ***** Profile -Profile::Profile(const char* name) +Profile::Profile(ProfileType device, const char* name) { + Type = device; Gender = Gender_Unspecified; - PlayerHeight = DEFAULT_HEIGHT; - IPD = DEFAULT_IPD; - Name = name; + 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; } + // Computes the eye height from the metric head height -float Profile::GetEyeHeight() const +float Profile::GetEyeHeight() { 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) @@ -702,4 +720,107 @@ float Profile::GetEyeHeight() const 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 2c5e4c2..df25fea 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,12 +25,11 @@ 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 ProfileDeviceType +enum ProfileType { Profile_Unknown = 0, Profile_GenericHMD = 10, @@ -38,17 +37,119 @@ enum ProfileDeviceType 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: // -// HMDProfile and it's child classes, RiftProfile, RiftDk1Profile and -// RiftDKHDProfile represent the intersection of 'per-user' and 'per-device' -// settings. -// +// { +// 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 // The generic HMD profile is used for properties that are common to all headsets -class HmdDevice +class HMDProfile : public Profile { protected: // FOV extents in pixels measured by a user @@ -58,7 +159,8 @@ protected: int RR; // right eye outer extent public: - virtual ~HmdDevice() {} + virtual Profile* Clone() const; + void SetLL(int val) { LL = val; }; void SetLR(int val) { LR = val; }; void SetRL(int val) { RL = val; }; @@ -68,19 +170,13 @@ public: int GetLR() { return LR; }; int GetRL() { return RL; }; int GetRR() { return RR; }; - virtual ProfileDeviceType GetDeviceType() const { - return Profile_GenericHMD; - } protected: - HmdDevice() { - LL = 0; - LR = 0; - RL = 0; - RR = 0; - } - friend class ProfileLoader; - friend class Profile; + HMDProfile(ProfileType type, const char* name); + + virtual bool ParseProperty(const char* prop, const char* sval); + + friend class ProfileManager; }; // For headsets that use eye cups @@ -91,259 +187,52 @@ enum EyeCupType EyeCup_C = 2 }; -class RiftDevice : public HmdDevice { +//----------------------------------------------------------------------------- +// ***** 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 +{ 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: - RiftDevice() { - EyeCups = EyeCup_A; - } - friend class ProfileLoader; - friend class Profile; -}; + RiftDK1Profile(const char* name); -//----------------------------------------------------------------------------- -// ***** RiftDK1Profile - -// 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; - } + virtual bool ParseProperty(const char* prop, const char* sval); -protected: - RiftDK1Device() { - - } - friend class ProfileLoader; - friend class Profile; + friend class ProfileManager; }; //----------------------------------------------------------------------------- // ***** 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 RiftDKHDDevice : public RiftDevice +class RiftDKHDProfile : public HMDProfile { -public: - virtual ProfileDeviceType GetDeviceType() const { - return Profile_RiftDKHD; - } - protected: - RiftDKHDDevice() { - } - friend class ProfileLoader; - friend class Profile; -}; + EyeCupType EyeCups; // Which eye cup does the player use -template <class KeyType> -class KeyedObject { public: - virtual ~KeyedObject() { - } - virtual const KeyType & GetKey() const = 0; -}; -//------------------------------------------------------------------- -// ***** Profile + virtual Profile* Clone() const; -// 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 + EyeCupType GetEyeCup() { return EyeCups; }; + void SetEyeCup(EyeCupType cup) { EyeCups = cup; }; protected: - 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; + RiftDKHDProfile(const char* name); -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); - } + virtual bool ParseProperty(const char* prop, const char* sval); -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(); }; @@ -351,4 +240,4 @@ String GetBaseOVRPath(bool create_dir); } -#endif // OVR_Profile_h +#endif // OVR_Profile_h
\ No newline at end of file |