From ac42a6925866bac9196494aa9e70ff24ed69abe4 Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Thu, 4 Aug 2022 14:05:52 -0400 Subject: [PATCH 01/38] Changed TrialCount --> TrialConfig --- source/ExperimentConfig.cpp | 6 +- source/FPSciApp.cpp | 265 +++++++++++++++++++----------------- source/FPSciApp.h | 10 +- source/FPSciGraphics.cpp | 26 ++-- source/Session.cpp | 13 +- source/Session.h | 19 ++- 6 files changed, 180 insertions(+), 159 deletions(-) diff --git a/source/ExperimentConfig.cpp b/source/ExperimentConfig.cpp index 32d37630..f8845259 100644 --- a/source/ExperimentConfig.cpp +++ b/source/ExperimentConfig.cpp @@ -74,7 +74,7 @@ void ExperimentConfig::init() { sess60.id = "60Hz"; sess60.description = "60Hz trials"; sess60.render.frameRate = 60.0f; - sess60.trials = Array({ TrialCount(Array({ "static", "moving", "jumping" }), 2) }); + sess60.trials = Array({ TrialConfig(Array({ "static", "moving", "jumping" }), 2) }); sessions.append(sess60); @@ -82,7 +82,7 @@ void ExperimentConfig::init() { sess30.id = "30Hz"; sess30.description = "30Hz trials"; sess30.render.frameRate = 30.0f; - sess30.trials = Array({ TrialCount(Array({ "static", "moving", "jumping" }), 2) }); + sess30.trials = Array({ TrialConfig(Array({ "static", "moving", "jumping" }), 2) }); sessions.append(sess30); } @@ -178,7 +178,7 @@ bool ExperimentConfig::validate(bool throwException) const { for (SessionConfig session : sessions) { Array sessionTargetIds; // Build a list of target ids used in this session - for (TrialCount trial : session.trials) { + for (TrialConfig trial : session.trials) { for (String id : trial.ids) { if (!sessionTargetIds.contains(id)) sessionTargetIds.append(id); } } // Check each ID against the experiment targets array diff --git a/source/FPSciApp.cpp b/source/FPSciApp.cpp index 716acd2b..118e7acf 100644 --- a/source/FPSciApp.cpp +++ b/source/FPSciApp.cpp @@ -8,7 +8,7 @@ #include // Storage for configuration static vars -int TrialCount::defaultCount; +int TrialConfig::defaultCount; Array UserSessionStatus::defaultSessionOrder; bool UserSessionStatus::randomizeDefaults; @@ -98,7 +98,7 @@ void FPSciApp::openUserSettingsWindow() { /** Handle the user settings window visibility */ void FPSciApp::closeUserSettingsWindow() { - if (sessConfig->menu.allowUserSettingsSave) { // If the user could have saved their settings + if (trialConfig->menu.allowUserSettingsSave) { // If the user could have saved their settings saveUserConfig(true); // Save the user config (if it has changed) whenever this window is closed } if (!dialog) { // Don't allow the user menu to hide the mouse when a dialog box is open @@ -279,7 +279,7 @@ Array> FPSciApp::makeMaterials(shared_ptrcolors, complete); } else { - color = lerpColor(sessConfig->targetView.healthColors, complete); + color = lerpColor(trialConfig->targetView.healthColors, complete); } Color4 gloss; @@ -287,15 +287,15 @@ Array> FPSciApp::makeMaterials(shared_ptrgloss; } else { - gloss = sessConfig->targetView.gloss; + gloss = trialConfig->targetView.gloss; } Color4 emissive; if (notNull(tconfig) && tconfig->emissive.length() > 0) { emissive = lerpColor(tconfig->emissive, complete); } - else if(sessConfig->targetView.emissive.length() > 0) { - emissive = lerpColor(sessConfig->targetView.emissive, complete); + else if(trialConfig->targetView.emissive.length() > 0) { + emissive = lerpColor(trialConfig->targetView.emissive, complete); } else { emissive = color * 0.7f; // Historical behavior fallback for unspecified case @@ -434,7 +434,7 @@ void FPSciApp::makeGUI() { void FPSciApp::exportScene() { CFrame frame = scene()->typedEntity("player")->frame(); logPrintf("Player position is: [%f, %f, %f]\n", frame.translation.x, frame.translation.y, frame.translation.z); - String filename = Scene::sceneNameToFilename(sessConfig->scene.name); + String filename = Scene::sceneNameToFilename(trialConfig->scene.name); scene()->toAny().save(filename); // Save this w/o JSON format (breaks scene.Any file) } @@ -514,7 +514,7 @@ void FPSciApp::markSessComplete(String sessId) { m_userSettingsWindow->updateSessionDropDown(); } -void FPSciApp::updateParameters(int frameDelay, float frameRate) { +void FPSciApp::updateFrameParameters(int frameDelay, float frameRate) { // Apply frame lag displayLagFrames = frameDelay; lastSetFrameRate = frameRate; @@ -526,14 +526,14 @@ void FPSciApp::updateParameters(int frameDelay, float frameRate) { setFrameDuration(dt, simStepDuration()); } -void FPSciApp::initPlayer(bool setSpawnPosition) { +void FPSciApp::initPlayer(const shared_ptr config, bool setSpawnPosition) { shared_ptr pscene = typedScene(); shared_ptr player = scene()->typedEntity("player"); // Get player from the scene // Update the player camera - const String pcamName = sessConfig->scene.playerCamera; + const String pcamName = config->scene.playerCamera; playerCamera = pcamName.empty() ? scene()->defaultCamera() : scene()->typedEntity(pcamName); - alwaysAssertM(notNull(playerCamera), format("Scene %s does not contain a camera named \"%s\"!", sessConfig->scene.name, pcamName)); + alwaysAssertM(notNull(playerCamera), format("Scene %s does not contain a camera named \"%s\"!", config->scene.name, pcamName)); setActiveCamera(playerCamera); // Set gravity and camera field of view @@ -562,7 +562,7 @@ void FPSciApp::initPlayer(bool setSpawnPosition) { // Set the reset height String resetHeightSource = "scene configuration \"resetHeight\" parameter"; - float resetHeight = sessConfig->scene.resetHeight; + float resetHeight = config->scene.resetHeight; if (isnan(resetHeight)) { resetHeightSource = "scene.Any Physics \"minHeight\" field"; resetHeight = pscene->resetHeight(); @@ -574,7 +574,7 @@ void FPSciApp::initPlayer(bool setSpawnPosition) { player->setRespawnHeight(resetHeight); // Update the respawn heading - if (isnan(sessConfig->scene.spawnHeadingDeg)) { + if (isnan(config->scene.spawnHeadingDeg)) { if (setSpawnPosition) { // This is the first spawn in the scene // No SceneConfig spawn heading specified, get heading from scene.Any player entity heading field Point3 view_dir = playerCamera->frame().lookVector(); @@ -583,20 +583,20 @@ void FPSciApp::initPlayer(bool setSpawnPosition) { } } else { // Respawn heading specified by the scene config - player->setRespawnHeadingDegrees(sessConfig->scene.spawnHeadingDeg); + player->setRespawnHeadingDegrees(config->scene.spawnHeadingDeg); } // Set player respawn location float respawnPosHeight = player->respawnPosHeight(); // Report the respawn position height - if (sessConfig->scene.spawnPosition.isNaN()) { + if (config->scene.spawnPosition.isNaN()) { if (setSpawnPosition) { // This is the first spawn, copy the respawn position from the scene player->setRespawnPosition(player->frame().translation); respawnPosHeight = player->frame().translation.y; } } else { // Respawn position set by scene config - player->setRespawnPosition(sessConfig->scene.spawnPosition); - respawnPosHeight = sessConfig->scene.spawnPosition.y; + player->setRespawnPosition(config->scene.spawnPosition); + respawnPosHeight = config->scene.spawnPosition.y; respawnHeightSource = "scene configuration \"spawnPosition\" parameter"; } @@ -605,14 +605,14 @@ void FPSciApp::initPlayer(bool setSpawnPosition) { } // Set player values from session config - player->moveRate = &sessConfig->player.moveRate; - player->moveScale = &sessConfig->player.moveScale; - player->axisLock = &sessConfig->player.axisLock; - player->jumpVelocity = &sessConfig->player.jumpVelocity; - player->jumpInterval = &sessConfig->player.jumpInterval; - player->jumpTouch = &sessConfig->player.jumpTouch; - player->height = &sessConfig->player.height; - player->crouchHeight = &sessConfig->player.crouchHeight; + player->moveRate = &config->player.moveRate; + player->moveScale = &config->player.moveScale; + player->axisLock = &config->player.axisLock; + player->jumpVelocity = &config->player.jumpVelocity; + player->jumpInterval = &config->player.jumpInterval; + player->jumpTouch = &config->player.jumpTouch; + player->height = &config->player.height; + player->crouchHeight = &config->player.crouchHeight; // Respawn player player->respawn(); @@ -622,7 +622,7 @@ void FPSciApp::initPlayer(bool setSpawnPosition) { sess->initialHeadingRadians = player->heading(); } -void FPSciApp::updateSession(const String& id, bool forceReload) { +void FPSciApp::updateSession(const String& id, const bool forceSceneReload) { // Check for a valid ID (non-emtpy and Array ids; experimentConfig.getSessionIds(ids); @@ -640,32 +640,93 @@ void FPSciApp::updateSession(const String& id, bool forceReload) { sess = Session::create(this); } + // Update colored materials to choose from for target health + for (String id : sessConfig->getUniqueTargetIds()) { + shared_ptr tconfig = experimentConfig.getTargetConfigById(id); + materials.remove(id); + materials.set(id, makeMaterials(tconfig)); + } + + // Update the application w/ the session parameters + updateConfigParameters(sessConfig, forceSceneReload); + + // Handle results files + const String resultsDirPath = startupConfig.experimentList[experimentIdx].resultsDirPath; + + // Check for need to start latency logging and if so run the logger now + if (!FileSystem::isDirectory(resultsDirPath)) { + FileSystem::createDirectory(resultsDirPath); + } + + // Create and check log file name + const String logFileBasename = sessConfig->logger.logToSingleDb ? + experimentConfig.description + "_" + userStatusTable.currentUser + "_" + m_expConfigHash : + id + "_" + userStatusTable.currentUser + "_" + String(FPSciLogger::genFileTimestamp()); + const String logFilename = FilePath::makeLegalFilename(logFileBasename); + // This is the specified path and log basename with illegal characters replaced, but not suffix (.db) + const String logPath = resultsDirPath + logFilename; + + // Logger only supported at the session-level + if (systemConfig.hasLogger) { + if (!sessConfig->clickToPhoton.enabled) { + logPrintf("WARNING: Using a click-to-photon logger without the click-to-photon region enabled!\n\n"); + } + if (m_pyLogger == nullptr) { + m_pyLogger = PythonLogger::create(systemConfig.loggerComPort, systemConfig.hasSync, systemConfig.syncComPort); + } + else { + // Handle running logger if we need to (terminate then merge results) + m_pyLogger->mergeLogToDb(); + } + // Run a new logger if we need to (include the mode to run in here...) + m_pyLogger->run(logPath, sessConfig->clickToPhoton.mode); + } + + // Initialize the experiment (this creates the results file) + sess->onInit(logPath, experimentConfig.description + "/" + sessConfig->description); + + // Don't create a results file for a user w/ no sessions left + if (m_userSettingsWindow->sessionsForSelectedUser() == 0) { + logPrintf("No sessions remaining for selected user.\n"); + } + else if (sessConfig->logger.enable) { + logPrintf("Created results file: %s.db\n", logPath.c_str()); + } + + if (m_firstSession) { + m_firstSession = false; + } +} + +void FPSciApp::updateConfigParameters(const shared_ptr config, const bool forceSceneReload) { + trialConfig = config; // Naive way to store trial config pointer for now + // Update reticle - reticleConfig.index = sessConfig->reticle.indexSpecified ? sessConfig->reticle.index : currentUser()->reticle.index; - reticleConfig.scale = sessConfig->reticle.scaleSpecified ? sessConfig->reticle.scale : currentUser()->reticle.scale; - reticleConfig.color = sessConfig->reticle.colorSpecified ? sessConfig->reticle.color : currentUser()->reticle.color; - reticleConfig.changeTimeS = sessConfig->reticle.changeTimeSpecified ? sessConfig->reticle.changeTimeS : currentUser()->reticle.changeTimeS; + reticleConfig.index = config->reticle.indexSpecified ? config->reticle.index : currentUser()->reticle.index; + reticleConfig.scale = config->reticle.scaleSpecified ? config->reticle.scale : currentUser()->reticle.scale; + reticleConfig.color = config->reticle.colorSpecified ? config->reticle.color : currentUser()->reticle.color; + reticleConfig.changeTimeS = config->reticle.changeTimeSpecified ? config->reticle.changeTimeS : currentUser()->reticle.changeTimeS; setReticle(reticleConfig.index); // Update the controls for this session updateControls(m_firstSession); // If first session consider showing the menu // Update the frame rate/delay - updateParameters(sessConfig->render.frameDelay, sessConfig->render.frameRate); + updateFrameParameters(config->render.frameDelay, config->render.frameRate); // Handle buffer setup here - updateShaderBuffers(); + updateShaderBuffers(config); // Update shader table m_shaderTable.clear(); - if (!sessConfig->render.shader3D.empty()) { - m_shaderTable.set(sessConfig->render.shader3D, G3D::Shader::getShaderFromPattern(sessConfig->render.shader3D)); + if (!config->render.shader3D.empty()) { + m_shaderTable.set(config->render.shader3D, G3D::Shader::getShaderFromPattern(config->render.shader3D)); } - if (!sessConfig->render.shader2D.empty()) { - m_shaderTable.set(sessConfig->render.shader2D, G3D::Shader::getShaderFromPattern(sessConfig->render.shader2D)); + if (!config->render.shader2D.empty()) { + m_shaderTable.set(config->render.shader2D, G3D::Shader::getShaderFromPattern(config->render.shader2D)); } - if (!sessConfig->render.shaderComposite.empty()) { - m_shaderTable.set(sessConfig->render.shaderComposite, G3D::Shader::getShaderFromPattern(sessConfig->render.shaderComposite)); + if (!config->render.shaderComposite.empty()) { + m_shaderTable.set(config->render.shaderComposite, G3D::Shader::getShaderFromPattern(config->render.shaderComposite)); } // Update shader parameters @@ -676,101 +737,49 @@ void FPSciApp::updateSession(const String& id, bool forceReload) { m_frameNumber = 0; // Load (session dependent) fonts - hudFont = GFont::fromFile(System::findDataFile(sessConfig->hud.hudFont)); - m_combatFont = GFont::fromFile(System::findDataFile(sessConfig->targetView.combatTextFont)); + hudFont = GFont::fromFile(System::findDataFile(config->hud.hudFont)); + m_combatFont = GFont::fromFile(System::findDataFile(config->targetView.combatTextFont)); // Handle clearing the targets here (clear any remaining targets before loading a new scene) if (notNull(scene())) sess->clearTargets(); // Load the experiment scene if we haven't already (target only) - if (sessConfig->scene.name.empty()) { + if (config->scene.name.empty()) { // No scene specified, load default scene - if (m_loadedScene.name.empty() || forceReload) { + if (m_loadedScene.name.empty() || forceSceneReload) { loadScene(m_defaultSceneName); // Note: this calls onGraphics() m_loadedScene.name = m_defaultSceneName; } // Otherwise let the loaded scene persist } - else if (sessConfig->scene != m_loadedScene || forceReload) { - loadScene(sessConfig->scene.name); - m_loadedScene = sessConfig->scene; + else if (config->scene != m_loadedScene || forceSceneReload) { + loadScene(config->scene.name); + m_loadedScene = config->scene; } // Player parameters - initPlayer(); + initPlayer(config); // Check for play mode specific parameters if (notNull(weapon)) weapon->clearDecals(); - weapon->setConfig(&sessConfig->weapon); + weapon->setConfig(&config->weapon); weapon->setScene(scene()); weapon->setCamera(activeCamera()); // Update weapon model (if drawn) and sounds weapon->loadModels(); weapon->loadSounds(); - if (!sessConfig->audio.sceneHitSound.empty()) { - m_sceneHitSound = Sound::create(System::findDataFile(sessConfig->audio.sceneHitSound)); + if (!config->audio.sceneHitSound.empty()) { + m_sceneHitSound = Sound::create(System::findDataFile(config->audio.sceneHitSound)); } - if (!sessConfig->audio.refTargetHitSound.empty()) { - m_refTargetHitSound = Sound::create(System::findDataFile(sessConfig->audio.refTargetHitSound)); + if (!config->audio.refTargetHitSound.empty()) { + m_refTargetHitSound = Sound::create(System::findDataFile(config->audio.refTargetHitSound)); } // Load static HUD textures - for (StaticHudElement element : sessConfig->hud.staticElements) { + for (StaticHudElement element : config->hud.staticElements) { hudTextures.set(element.filename, Texture::fromFile(System::findDataFile(element.filename))); } - - // Update colored materials to choose from for target health - for (String id : sessConfig->getUniqueTargetIds()) { - shared_ptr tconfig = experimentConfig.getTargetConfigById(id); - materials.remove(id); - materials.set(id, makeMaterials(tconfig)); - } - - const String resultsDirPath = startupConfig.experimentList[experimentIdx].resultsDirPath; - - // Check for need to start latency logging and if so run the logger now - if (!FileSystem::isDirectory(resultsDirPath)) { - FileSystem::createDirectory(resultsDirPath); - } - - // Create and check log file name - const String logFileBasename = sessConfig->logger.logToSingleDb ? - experimentConfig.description + "_" + userStatusTable.currentUser + "_" + m_expConfigHash : - id + "_" + userStatusTable.currentUser + "_" + String(FPSciLogger::genFileTimestamp()); - const String logFilename = FilePath::makeLegalFilename(logFileBasename); - // This is the specified path and log basename with illegal characters replaced, but not suffix (.db) - const String logPath = resultsDirPath + logFilename; - - if (systemConfig.hasLogger) { - if (!sessConfig->clickToPhoton.enabled) { - logPrintf("WARNING: Using a click-to-photon logger without the click-to-photon region enabled!\n\n"); - } - if (m_pyLogger == nullptr) { - m_pyLogger = PythonLogger::create(systemConfig.loggerComPort, systemConfig.hasSync, systemConfig.syncComPort); - } - else { - // Handle running logger if we need to (terminate then merge results) - m_pyLogger->mergeLogToDb(); - } - // Run a new logger if we need to (include the mode to run in here...) - m_pyLogger->run(logPath, sessConfig->clickToPhoton.mode); - } - - // Initialize the experiment (this creates the results file) - sess->onInit(logPath, experimentConfig.description + "/" + sessConfig->description); - - // Don't create a results file for a user w/ no sessions left - if (m_userSettingsWindow->sessionsForSelectedUser() == 0) { - logPrintf("No sessions remaining for selected user.\n"); - } - else if(sessConfig->logger.enable) { - logPrintf("Created results file: %s.db\n", logPath.c_str()); - } - - if (m_firstSession) { - m_firstSession = false; - } } void FPSciApp::quitRequest() { @@ -803,7 +812,7 @@ void FPSciApp::onAfterLoadScene(const Any& any, const String& sceneName) { m_initialCameraFrames.set(cam->name(), cam->frame()); } - initPlayer(true); // Initialize the player (first time for this scene) + initPlayer(trialConfig, true); // Initialize the player (first time for this scene) if (weapon) { weapon->setScene(scene()); @@ -898,7 +907,7 @@ void FPSciApp::onSimulation(RealTime rdt, SimTime sdt, SimTime idt) { { // Play scene hit sound if (!weapon->config()->isContinuous() && notNull(m_sceneHitSound)) { - m_sceneHitSound->play(sessConfig->audio.sceneHitSoundVol); + m_sceneHitSound->play(trialConfig->audio.sceneHitSoundVol); } } shotFired = true; @@ -950,8 +959,8 @@ void FPSciApp::onSimulation(RealTime rdt, SimTime sdt, SimTime idt) { } // Handle frame rate/delay updates here - if (sessConfig->render.frameRate != lastSetFrameRate || displayLagFrames != sessConfig->render.frameDelay) { - updateParameters(sessConfig->render.frameDelay, sessConfig->render.frameRate); + if (trialConfig->render.frameRate != lastSetFrameRate || displayLagFrames != trialConfig->render.frameDelay) { + updateFrameParameters(trialConfig->render.frameDelay, trialConfig->render.frameRate); } if (notNull(waypointManager)) { @@ -1151,7 +1160,7 @@ bool FPSciApp::onEvent(const GEvent& event) { // Handle resize event here if (event.type == GEventType::VIDEO_RESIZE) { // Resize the shader buffers here - updateShaderBuffers(); + updateShaderBuffers(trialConfig); } // Handle super-class events @@ -1165,7 +1174,7 @@ void FPSciApp::onAfterEvents() { // Re-create the settings window String selSess = m_userSettingsWindow->selectedSession(); - m_userSettingsWindow = UserMenu::create(this, userTable, userStatusTable, sessConfig->menu, theme, Rect2D::xywh(0.0f, 0.0f, 10.0f, 10.0f)); + m_userSettingsWindow = UserMenu::create(this, userTable, userStatusTable, trialConfig->menu, theme, Rect2D::xywh(0.0f, 0.0f, 10.0f, 10.0f)); m_userSettingsWindow->setSelectedSession(selSess); moveToCenter(m_userSettingsWindow); m_userSettingsWindow->setVisible(m_showUserMenu); @@ -1189,7 +1198,7 @@ void FPSciApp::onAfterEvents() { Vector2 FPSciApp::currentTurnScale() { const shared_ptr user = currentUser(); - Vector2 baseTurnScale = sessConfig->player.turnScale * user->turnScale; + Vector2 baseTurnScale = trialConfig->player.turnScale * user->turnScale; // Apply y-invert here if (user->invertY) baseTurnScale.y = -baseTurnScale.y; // If we're not scoped just return the normal user turn scale @@ -1201,16 +1210,16 @@ Vector2 FPSciApp::currentTurnScale() { } else { // Otherwise scale the scope turn scalue using the ratio of FoV - return playerCamera->fieldOfViewAngleDegrees() / sessConfig->render.hFoV * baseTurnScale; + return playerCamera->fieldOfViewAngleDegrees() / trialConfig->render.hFoV * baseTurnScale; } } void FPSciApp::setScopeView(bool scoped) { // Get player entity and calculate scope FoV const shared_ptr& player = scene()->typedEntity("player"); - const float scopeFoV = sessConfig->weapon.scopeFoV > 0 ? sessConfig->weapon.scopeFoV : sessConfig->render.hFoV; - weapon->setScoped(scoped); // Update the weapon state - const float FoV = (scoped ? scopeFoV : sessConfig->render.hFoV); // Get new FoV in degrees (depending on scope state) + const float scopeFoV = trialConfig->weapon.scopeFoV > 0 ? trialConfig->weapon.scopeFoV : trialConfig->render.hFoV; + weapon->setScoped(scoped); // Update the weapon state + const float FoV = (scoped ? scopeFoV : trialConfig->render.hFoV); // Get new FoV in degrees (depending on scope state) playerCamera->setFieldOfView(FoV * pif() / 180.f, FOVDirection::HORIZONTAL); // Set the camera FoV player->turnScale = currentTurnScale(); // Scale sensitivity based on the field of view change here } @@ -1222,17 +1231,17 @@ void FPSciApp::hitTarget(shared_ptr target) { target->playHitSound(); // Check if we need to add combat text for this damage - if (sessConfig->targetView.showCombatText) { + if (trialConfig->targetView.showCombatText) { m_combatTextList.append(FloatingCombatText::create( format("%2.0f", 100.f * damage), m_combatFont, - sessConfig->targetView.combatTextSize, - sessConfig->targetView.combatTextColor, - sessConfig->targetView.combatTextOutline, - sessConfig->targetView.combatTextOffset, - sessConfig->targetView.combatTextVelocity, - sessConfig->targetView.combatTextFade, - sessConfig->targetView.combatTextTimeout)); + trialConfig->targetView.combatTextSize, + trialConfig->targetView.combatTextColor, + trialConfig->targetView.combatTextOutline, + trialConfig->targetView.combatTextOffset, + trialConfig->targetView.combatTextVelocity, + trialConfig->targetView.combatTextFade, + trialConfig->targetView.combatTextTimeout)); m_combatTextList.last()->setFrame(target->frame()); } @@ -1243,7 +1252,7 @@ void FPSciApp::hitTarget(shared_ptr target) { // Handle reference target here sess->destroyTarget(target); if (notNull(m_refTargetHitSound)) { - m_refTargetHitSound->play(sessConfig->audio.refTargetHitSoundVol); + m_refTargetHitSound->play(trialConfig->audio.refTargetHitSoundVol); } destroyedTarget = true; sess->accumulatePlayerAction(PlayerActionType::Destroy, target->name()); @@ -1331,7 +1340,7 @@ void FPSciApp::onUserInput(UserInput* ui) { for (GKey scopeButton : keyMap.map["scope"]) { if (ui->keyPressed(scopeButton)) { // Are we using scope toggling? - if (sessConfig->weapon.scopeToggle) { + if (trialConfig->weapon.scopeToggle) { setScopeView(!weapon->scoped()); } // Otherwise just set scope based on the state of the scope button @@ -1339,7 +1348,7 @@ void FPSciApp::onUserInput(UserInput* ui) { setScopeView(true); } } - if (ui->keyReleased(scopeButton) && !sessConfig->weapon.scopeToggle) { + if (ui->keyReleased(scopeButton) && !trialConfig->weapon.scopeToggle) { setScopeView(false); } } @@ -1375,7 +1384,7 @@ void FPSciApp::onUserInput(UserInput* ui) { float hitDist = finf(); int hitIdx = -1; shared_ptr target = weapon->fire(sess->hittableTargets(), hitIdx, hitDist, info, dontHit, true); // Fire the weapon - if (sessConfig->audio.refTargetPlayFireSound && !sessConfig->weapon.loopAudio()) { // Only play shot sounds for non-looped weapon audio (continuous/automatic fire not allowed) + if (trialConfig->audio.refTargetPlayFireSound && !trialConfig->weapon.loopAudio()) { // Only play shot sounds for non-looped weapon audio (continuous/automatic fire not allowed) weapon->playSound(true, false); // Play audio here for reference target } } @@ -1383,7 +1392,7 @@ void FPSciApp::onUserInput(UserInput* ui) { if (m_lastReticleLoaded != currentUser()->reticle.index || m_userSettingsWindow->visible()) { // Slider was used to change the reticle - if (!sessConfig->reticle.indexSpecified) { // Only allow reticle change if it isn't specified in experiment config + if (!trialConfig->reticle.indexSpecified) { // Only allow reticle change if it isn't specified in experiment config setReticle(currentUser()->reticle.index); m_userSettingsWindow->updateReticlePreview(); } diff --git a/source/FPSciApp.h b/source/FPSciApp.h index aaff3ae6..965b7866 100644 --- a/source/FPSciApp.h +++ b/source/FPSciApp.h @@ -137,7 +137,7 @@ class FPSciApp : public GApp { /** Initializes player settings from configs and resets player to initial position Also updates mouse sensitivity. */ - void initPlayer(bool firstSpawn = false); + void initPlayer(const shared_ptr config, bool firstSpawn = false); /** Move a window to the center of the display */ void moveToCenter(shared_ptr window) { @@ -186,6 +186,7 @@ class FPSciApp : public GApp { shared_ptr waypointManager; ///< Waypoint mananger pointer shared_ptr sessConfig = SessionConfig::create(); ///< Current session config + shared_ptr trialConfig = TrialConfig::create(); ///< Current trial config shared_ptr dialog; ///< Dialog box Question currentQuestion; ///< Currently presented question @@ -253,8 +254,9 @@ class FPSciApp : public GApp { void markSessComplete(String id); /** Updates experiment state to the provided session id and updates player parameters (including mouse sensitivity) */ - virtual void updateSession(const String& id, bool forceReload = false); - void updateParameters(int frameDelay, float frameRate); + virtual void updateSession(const String& id, bool forceSceneReload = false); + void updateConfigParameters(const shared_ptr config, bool forceSceneReload = false); + void updateFrameParameters(int frameDelay, float frameRate); void updateTargetColor(const shared_ptr& target); void presentQuestion(Question question); @@ -299,7 +301,7 @@ class FPSciApp : public GApp { void updateFPSIndicator(RenderDevice* rd, Vector2 resolution); ///< Update and draw a (custom) frame time indicator (developer mode feature) void drawFeedbackMessage(RenderDevice* rd); ///< Draw a user feedback message (at full render device resolution) - void updateShaderBuffers(); ///< Regenerate buffers (for configured shaders) + void updateShaderBuffers(const shared_ptr config); ///< Regenerate buffers (for configured shaders) /** calls rd->pushState with the right delayed buffer. Creates buffers if needed */ void pushRdStateWithDelay(RenderDevice* rd, Array> &delayBufferQueue, int &delayIndex, int lagFrames = 0); diff --git a/source/FPSciGraphics.cpp b/source/FPSciGraphics.cpp index 97038173..468aeae2 100644 --- a/source/FPSciGraphics.cpp +++ b/source/FPSciGraphics.cpp @@ -2,16 +2,16 @@ #include "FPSciApp.h" #include "WaypointManager.h" -void FPSciApp::updateShaderBuffers() { +void FPSciApp::updateShaderBuffers(const shared_ptr config) { // Parameters for update/resize of buffers int width = renderDevice->width(); int height = renderDevice->height(); // 2D buffers (input and output) used when 2D resolution or shader is specified - if (!sessConfig->render.shader2D.empty() || sessConfig->render.resolution2D[0] > 0) { - if (sessConfig->render.resolution2D[0] > 0) { - width = sessConfig->render.resolution2D[0]; - height = sessConfig->render.resolution2D[1]; + if (!config->render.shader2D.empty() || config->render.resolution2D[0] > 0) { + if (config->render.resolution2D[0] > 0) { + width = config->render.resolution2D[0]; + height = config->render.resolution2D[1]; } m_ldrBuffer2D = Framebuffer::create(Texture::createEmpty("FPSci::2DShaderPass::Input", width, height, ImageFormat::RGBA8(), Texture::DIM_2D, true)); @@ -24,11 +24,11 @@ void FPSciApp::updateShaderBuffers() { } // 3D shader output (use popped framebuffer as input) used when 3D resolution or shader is specified - if (!sessConfig->render.shader3D.empty() || sessConfig->render.resolution3D[0] > 0) { + if (!config->render.shader3D.empty() || config->render.resolution3D[0] > 0) { width = m_framebuffer->width(); height = m_framebuffer->height(); - if (sessConfig->render.resolution3D[0] > 0) { - width = sessConfig->render.resolution3D[0]; - height = sessConfig->render.resolution3D[1]; + if (config->render.resolution3D[0] > 0) { + width = config->render.resolution3D[0]; + height = config->render.resolution3D[1]; } m_hdrShader3DOutput = Framebuffer::create(Texture::createEmpty("FPSci::3DShaderPass::Output", width, height, m_framebuffer->texture(0)->format(), Texture::DIM_2D, true)); @@ -38,13 +38,13 @@ void FPSciApp::updateShaderBuffers() { } // Composite buffer (input and output) used when composite shader or resolution is specified - if (!sessConfig->render.shaderComposite.empty() || sessConfig->render.resolutionComposite[0] > 0) { + if (!config->render.shaderComposite.empty() || config->render.resolutionComposite[0] > 0) { width = renderDevice->width(); height = renderDevice->height(); m_ldrBufferPrecomposite = Framebuffer::create(Texture::createEmpty("FPSci::CompositeShaderPass::Precomposite", width, height, ImageFormat::RGB8(), Texture::DIM_2D, true)); - if (sessConfig->render.resolutionComposite[0] > 0) { - width = sessConfig->render.resolutionComposite[0]; - height = sessConfig->render.resolutionComposite[1]; + if (config->render.resolutionComposite[0] > 0) { + width = config->render.resolutionComposite[0]; + height = config->render.resolutionComposite[1]; } m_ldrBufferComposite = Framebuffer::create(Texture::createEmpty("FPSci::CompositeShaderPass::Input", width, height, ImageFormat::RGB8(), Texture::DIM_2D, true)); diff --git a/source/Session.cpp b/source/Session.cpp index 8176a259..66f30d1d 100644 --- a/source/Session.cpp +++ b/source/Session.cpp @@ -34,7 +34,7 @@ #include "Weapon.h" #include "FPSciAnyTableReader.h" -TrialCount::TrialCount(const Any& any) { +TrialConfig::TrialConfig(const Any& any) : FpsConfig(any, defaultConfig()) { int settingsVersion = 1; FPSciAnyTableReader reader(any); reader.getIfPresent("settingsVersion", settingsVersion); @@ -55,18 +55,19 @@ TrialCount::TrialCount(const Any& any) { } } -Any TrialCount::toAny(const bool forceAll) const { - Any a(Any::TABLE); +Any TrialConfig::toAny(const bool forceAll) const { + Any a = FpsConfig::toAny(forceAll); a["ids"] = ids; a["count"] = count; return a; } SessionConfig::SessionConfig(const Any& any) : FpsConfig(any, defaultConfig()) { - TrialCount::defaultCount = timing.defaultTrialCount; + TrialConfig::defaultCount = timing.defaultTrialCount; FPSciAnyTableReader reader(any); switch (settingsVersion) { case 1: + TrialConfig::defaultConfig() = (FpsConfig)(*this); // Setup the default configuration for trials here // Unique session info reader.get("id", id, "An \"id\" field must be provided for each session!"); reader.getIfPresent("description", description); @@ -96,7 +97,7 @@ Any SessionConfig::toAny(const bool forceAll) const { float SessionConfig::getTrialsPerBlock(void) const { float count = 0.f; - for (const TrialCount& tc : trials) { + for (const TrialConfig& tc : trials) { if (count < 0) { return finf(); } @@ -109,7 +110,7 @@ float SessionConfig::getTrialsPerBlock(void) const { Array SessionConfig::getUniqueTargetIds() const { Array ids; - for (TrialCount trial : trials) { + for (TrialConfig trial : trials) { for (String id : trial.ids) { if (!ids.contains(id)) { ids.append(id); } } diff --git a/source/Session.h b/source/Session.h index 86cd7bab..900a193c 100644 --- a/source/Session.h +++ b/source/Session.h @@ -119,15 +119,24 @@ struct PlayerAction { }; /** Trial count class (optional for alternate TargetConfig/count table lookup) */ -class TrialCount { +class TrialConfig : public FpsConfig { public: Array ids; ///< Trial ID list int count = 1; ///< Count of trials to be performed static int defaultCount; ///< Default count to use - TrialCount() {}; - TrialCount(const Array& trialIds, int trialCount) : ids(trialIds), count(trialCount) {}; - TrialCount(const Any& any); + TrialConfig() : FpsConfig(defaultConfig()) {}; + TrialConfig(const Array& trialIds, int trialCount) : ids(trialIds), count(trialCount) {}; + TrialConfig(const Any& any); + + static shared_ptr create() { return createShared(); } + + // Use a static method to bypass order of declaration for static members (specific to Sampler s_freeList in GLSamplerObect) + // Trick from: https://www.cs.technion.ac.il/users/yechiel/c++-faq/static-init-order-on-first-use.html + static FpsConfig& defaultConfig() { + static FpsConfig def; + return def; + } Any toAny(const bool forceAll = true) const; }; @@ -138,7 +147,7 @@ class SessionConfig : public FpsConfig { String id; ///< Session ID String description = "Session"; ///< String indicating whether session is training or real int blockCount = 1; ///< Default to just 1 block per session - Array trials; ///< Array of trials (and their counts) to be performed + Array trials; ///< Array of trials (and their counts) to be performed bool closeOnComplete = false; ///< Close application on session completed? SessionConfig() : FpsConfig(defaultConfig()) {} From ead38b246318c9f85ba91de867eacba00bef72f0 Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Fri, 5 Aug 2022 12:54:18 -0400 Subject: [PATCH 02/38] Start incorporating TrialConfig --- source/FPSciApp.cpp | 5 +- source/FPSciApp.h | 1 + source/Session.cpp | 162 ++++++++++++++++++++++---------------------- source/Session.h | 7 +- 4 files changed, 91 insertions(+), 84 deletions(-) diff --git a/source/FPSciApp.cpp b/source/FPSciApp.cpp index 118e7acf..ad2b1480 100644 --- a/source/FPSciApp.cpp +++ b/source/FPSciApp.cpp @@ -698,9 +698,12 @@ void FPSciApp::updateSession(const String& id, const bool forceSceneReload) { } } -void FPSciApp::updateConfigParameters(const shared_ptr config, const bool forceSceneReload) { +void FPSciApp::updateTrial(const shared_ptr config, bool forceSceneReload) { trialConfig = config; // Naive way to store trial config pointer for now + updateConfigParameters(config, forceSceneReload); +} +void FPSciApp::updateConfigParameters(const shared_ptr config, const bool forceSceneReload) { // Update reticle reticleConfig.index = config->reticle.indexSpecified ? config->reticle.index : currentUser()->reticle.index; reticleConfig.scale = config->reticle.scaleSpecified ? config->reticle.scale : currentUser()->reticle.scale; diff --git a/source/FPSciApp.h b/source/FPSciApp.h index 965b7866..a60b168c 100644 --- a/source/FPSciApp.h +++ b/source/FPSciApp.h @@ -255,6 +255,7 @@ class FPSciApp : public GApp { void markSessComplete(String id); /** Updates experiment state to the provided session id and updates player parameters (including mouse sensitivity) */ virtual void updateSession(const String& id, bool forceSceneReload = false); + void updateTrial(const shared_ptr config, bool forceSceneReload = false); void updateConfigParameters(const shared_ptr config, bool forceSceneReload = false); void updateFrameParameters(int frameDelay, float frameRate); void updateTargetColor(const shared_ptr& target); diff --git a/source/Session.cpp b/source/Session.cpp index 66f30d1d..130a6fc0 100644 --- a/source/Session.cpp +++ b/source/Session.cpp @@ -118,8 +118,8 @@ Array SessionConfig::getUniqueTargetIds() const { return ids; } -Session::Session(FPSciApp* app, shared_ptr config) : m_app(app), m_config(config), m_weapon(app->weapon) { - m_hasSession = notNull(m_config); +Session::Session(FPSciApp* app, shared_ptr config) : m_app(app), m_sessConfig(config), m_weapon(app->weapon) { + m_hasSession = notNull(m_sessConfig); } Session::Session(FPSciApp* app) : m_app(app), m_weapon(app->weapon) { @@ -138,18 +138,18 @@ const RealTime Session::targetFrameTime() const RealTime defaultFrameTime = 1.0 / m_app->window()->settings().refreshRate; if (!m_hasSession) return defaultFrameTime; - uint arraySize = m_config->render.frameTimeArray.size(); + uint arraySize = m_trialConfig->render.frameTimeArray.size(); if (arraySize > 0) { - if ((m_config->render.frameTimeMode == "taskonly" || m_config->render.frameTimeMode == "restartwithtask") && currentState != PresentationState::trialTask) { + if ((m_trialConfig->render.frameTimeMode == "taskonly" || m_trialConfig->render.frameTimeMode == "restartwithtask") && currentState != PresentationState::trialTask) { // We are in a frame time mode which specifies only to change frame time during the task - return 1.0f / m_config->render.frameRate; + return 1.0f / m_trialConfig->render.frameRate; } - if (m_config->render.frameTimeRandomize) { - return m_config->render.frameTimeArray.randomElement(); + if (m_trialConfig->render.frameTimeRandomize) { + return m_trialConfig->render.frameTimeArray.randomElement(); } else { - RealTime targetTime = m_config->render.frameTimeArray[m_frameTimeIdx % arraySize]; + RealTime targetTime = m_trialConfig->render.frameTimeArray[m_frameTimeIdx % arraySize]; m_frameTimeIdx += 1; m_frameTimeIdx %= arraySize; return targetTime; @@ -157,8 +157,8 @@ const RealTime Session::targetFrameTime() } // The below matches the functionality in FPSciApp::updateParameters() - if (m_config->render.frameRate > 0) { - return 1.0f / m_config->render.frameRate; + if (m_trialConfig->render.frameRate > 0) { + return 1.0f / m_trialConfig->render.frameRate; } return defaultFrameTime; } @@ -173,10 +173,12 @@ bool Session::nextCondition() { if (unrunTrialIdxs.size() == 0) return false; int idx = Random::common().integer(0, unrunTrialIdxs.size()-1); m_currTrialIdx = unrunTrialIdxs[idx]; + m_trialConfig = shared_ptr(&(m_sessConfig->trials[m_currTrialIdx])); + m_app->updateTrial(m_trialConfig); // Produce (potentially random in range) pretrial duration - if (isNaN(m_config->timing.pretrialDurationLambda)) m_pretrialDuration = m_config->timing.pretrialDuration; - else m_pretrialDuration = drawTruncatedExp(m_config->timing.pretrialDurationLambda, m_config->timing.pretrialDurationRange[0], m_config->timing.pretrialDurationRange[1]); + if (isNaN(m_trialConfig->timing.pretrialDurationLambda)) m_pretrialDuration = m_trialConfig->timing.pretrialDuration; + else m_pretrialDuration = drawTruncatedExp(m_trialConfig->timing.pretrialDurationLambda, m_trialConfig->timing.pretrialDurationRange[0], m_trialConfig->timing.pretrialDurationRange[1]); return true; } @@ -193,12 +195,12 @@ bool Session::updateBlock(bool init) { for (int i = 0; i < m_trials.size(); i++) { const Array>& targets = m_trials[i]; if (init) { // If this is the first block in the session - m_completedTrials.append(0); // This increments across blocks (unique trial index) - m_remainingTrials.append(m_config->trials[i].count); // This is reset across blocks (tracks progress) - m_targetConfigs.append(targets); // This only needs to be setup once + m_completedTrials.append(0); // This increments across blocks (unique trial index) + m_remainingTrials.append(m_trialConfig->count); // This is reset across blocks (tracks progress) + m_targetConfigs.append(targets); // This only needs to be setup once } else { // Update for a new block in the session - m_remainingTrials[i] += m_config->trials[i].count; // Add another set of trials of this type + m_remainingTrials[i] += m_trialConfig->count; // Add another set of trials of this type } } return nextCondition(); @@ -207,8 +209,8 @@ bool Session::updateBlock(bool init) { void Session::onInit(String filename, String description) { // Initialize presentation states currentState = PresentationState::initial; - if (m_config) { - m_feedbackMessage = formatFeedback(m_config->targetView.showRefTarget ? m_config->feedback.initialWithRef: m_config->feedback.initialNoRef); + if (m_sessConfig) { + m_feedbackMessage = formatFeedback(m_sessConfig->targetView.showRefTarget ? m_sessConfig->feedback.initialWithRef: m_sessConfig->feedback.initialNoRef); } // Get the player from the app @@ -220,21 +222,21 @@ void Session::onInit(String filename, String description) { // Check for valid session if (m_hasSession) { - if (m_config->logger.enable) { + if (m_sessConfig->logger.enable) { UserConfig user = *m_app->currentUser(); // Setup the logger and create results file logger = FPSciLogger::create(filename + ".db", user.id, m_app->startupConfig.experimentList[m_app->experimentIdx].experimentConfigFilename, - m_config, description); - logger->logTargetTypes(m_app->experimentConfig.getSessionTargets(m_config->id)); // Log target info at start of session - logger->logUserConfig(user, m_config->id, m_config->player.turnScale); // Log user info at start of session + m_sessConfig, description); + logger->logTargetTypes(m_app->experimentConfig.getSessionTargets(m_sessConfig->id)); // Log target info at start of session + logger->logUserConfig(user, m_sessConfig->id, m_sessConfig->player.turnScale); // Log user info at start of session m_dbFilename = filename; } runSessionCommands("start"); // Run start of session commands // Iterate over the sessions here and add a config for each - m_trials = m_app->experimentConfig.getTargetsByTrial(m_config->id); + m_trials = m_app->experimentConfig.getTargetsByTrial(m_sessConfig->id); updateBlock(true); } else { // Invalid session, move to displaying message @@ -268,7 +270,7 @@ void Session::initTargetAnimation() { // In task state, spawn a test target. Otherwise spawn a target at straight ahead. if (currentState == PresentationState::trialTask) { - if (m_config->targetView.previewWithRef && m_config->targetView.showRefTarget) { + if (m_trialConfig->targetView.previewWithRef && m_trialConfig->targetView.showRefTarget) { // Activate the preview targets for (shared_ptr target : m_targetArray) { target->setCanHit(true); @@ -287,18 +289,18 @@ void Session::initTargetAnimation() { auto t = spawnReferenceTarget( f.pointToWorldSpace(Point3(0, 0, -m_targetDistance)), initialSpawnPos, - m_config->targetView.refTargetSize, - m_config->targetView.refTargetColor + m_trialConfig->targetView.refTargetSize, + m_trialConfig->targetView.refTargetColor ); m_hittableTargets.append(t); m_lastRefTargetPos = t->frame().translation; // Save last spawned reference target position - if (m_config->targetView.previewWithRef) { + if (m_trialConfig->targetView.previewWithRef) { spawnTrialTargets(initialSpawnPos, true); // Spawn all the targets in preview mode } // Set weapon decal state to match configuration for reference targets - m_weapon->drawsDecals = m_config->targetView.showRefDecals; + m_weapon->drawsDecals = m_trialConfig->targetView.showRefDecals; // Clear target logOnChange management m_lastLogTargetLoc.clear(); @@ -314,9 +316,9 @@ void Session::initTargetAnimation() { void Session::spawnTrialTargets(Point3 initialSpawnPos, bool previewMode) { // Iterate through the targets for (int i = 0; i < m_targetConfigs[m_currTrialIdx].size(); i++) { - const Color3 previewColor = m_config->targetView.previewColor; + const Color3 previewColor = m_trialConfig->targetView.previewColor; shared_ptr target = m_targetConfigs[m_currTrialIdx][i]; - const String name = format("%s_%d_%d_%s_%d", m_config->id, m_currTrialIdx, m_completedTrials[m_currTrialIdx], target->id, i); + const String name = format("%s_%d_%d_%s_%d", m_sessConfig->id, m_currTrialIdx, m_completedTrials[m_currTrialIdx], target->id, i); const float spawn_eccV = (target->symmetricEccV ? randSign() : 1) * Random::common().uniform(target->eccV[0], target->eccV[1]); const float spawn_eccH = (target->symmetricEccH ? randSign() : 1) * Random::common().uniform(target->eccH[0], target->eccH[1]); @@ -324,7 +326,7 @@ void Session::spawnTrialTargets(Point3 initialSpawnPos, bool previewMode) { bool isWorldSpace = target->destSpace == "world"; // Log the target if desired - if (m_config->logger.enable) { + if (m_sessConfig->logger.enable) { const String spawnTime = FPSciLogger::genUniqueTimestamp(); logger->addTarget(name, target, spawnTime, targetSize, Point2(spawn_eccH, spawn_eccV)); } @@ -378,11 +380,11 @@ void Session::processResponse() // Check for whether all targets have been destroyed if (m_destroyedTargets == totalTargets) { - m_totalRemainingTime += (double(m_config->timing.maxTrialDuration) - m_taskExecutionTime); - m_feedbackMessage = formatFeedback(m_config->feedback.trialSuccess); + m_totalRemainingTime += (double(m_trialConfig->timing.maxTrialDuration) - m_taskExecutionTime); + m_feedbackMessage = formatFeedback(m_trialConfig->feedback.trialSuccess); } else { - m_feedbackMessage = formatFeedback(m_config->feedback.trialFailure); + m_feedbackMessage = formatFeedback(m_trialConfig->feedback.trialFailure); } } @@ -396,10 +398,10 @@ void Session::updatePresentationState() if (currentState == PresentationState::initial) { - if (m_config->player.stillBetweenTrials) { + if (m_sessConfig->player.stillBetweenTrials) { m_player->setMoveEnable(false); } - if (!(m_app->shootButtonUp && m_config->timing.clickToStart)) { + if (!(m_app->shootButtonUp && m_trialConfig->timing.clickToStart)) { newState = PresentationState::trialFeedback; } } @@ -408,7 +410,7 @@ void Session::updatePresentationState() if (stateElapsedTime > m_pretrialDuration) { newState = PresentationState::trialTask; - if (m_config->player.stillBetweenTrials) { + if (m_sessConfig->player.stillBetweenTrials) { m_player->setMoveEnable(true); } @@ -419,16 +421,16 @@ void Session::updatePresentationState() } else if (currentState == PresentationState::trialTask) { - if ((stateElapsedTime > m_config->timing.maxTrialDuration) || (remainingTargets <= 0) || (m_weapon->remainingAmmo() == 0)) + if ((stateElapsedTime > m_trialConfig->timing.maxTrialDuration) || (remainingTargets <= 0) || (m_weapon->remainingAmmo() == 0)) { m_taskEndTime = FPSciLogger::genUniqueTimestamp(); processResponse(); clearTargets(); // clear all remaining targets newState = PresentationState::trialFeedback; - if (m_config->player.stillBetweenTrials) { + if (m_sessConfig->player.stillBetweenTrials) { m_player->setMoveEnable(false); } - if (m_config->player.resetPositionPerTrial) { + if (m_sessConfig->player.resetPositionPerTrial) { m_player->respawn(); } @@ -444,56 +446,56 @@ void Session::updatePresentationState() } else if (currentState == PresentationState::trialFeedback) { - if ((stateElapsedTime > m_config->timing.trialFeedbackDuration) && (remainingTargets <= 0)) + if ((stateElapsedTime > m_trialConfig->timing.trialFeedbackDuration) && (remainingTargets <= 0)) { if (blockComplete()) { m_currBlock++; // Increment the block index - if (m_currBlock > m_config->blockCount) { + if (m_currBlock > m_sessConfig->blockCount) { // Check for end of session (all blocks complete) - if (m_config->questionArray.size() > 0 && m_currQuestionIdx < m_config->questionArray.size()) { + if (m_sessConfig->questionArray.size() > 0 && m_currQuestionIdx < m_sessConfig->questionArray.size()) { // Pop up question dialog(s) here if we need to if (m_currQuestionIdx == -1) { m_currQuestionIdx = 0; - m_app->presentQuestion(m_config->questionArray[m_currQuestionIdx]); + m_app->presentQuestion(m_sessConfig->questionArray[m_currQuestionIdx]); } else if (!m_app->dialog->visible()) { // Check for whether dialog is closed (otherwise we are waiting for input) if (m_app->dialog->complete) { // Has this dialog box been completed? (or was it closed without an answer?) - m_config->questionArray[m_currQuestionIdx].result = m_app->dialog->result; // Store response w/ quesiton - if (m_config->logger.enable) { - logger->addQuestion(m_config->questionArray[m_currQuestionIdx], m_config->id, m_app->dialog); // Log the question and its answer + m_sessConfig->questionArray[m_currQuestionIdx].result = m_app->dialog->result; // Store response w/ quesiton + if (m_sessConfig->logger.enable) { + logger->addQuestion(m_sessConfig->questionArray[m_currQuestionIdx], m_sessConfig->id, m_app->dialog); // Log the question and its answer } m_currQuestionIdx++; - if (m_currQuestionIdx < m_config->questionArray.size()) { // Double check we have a next question before launching the next question - m_app->presentQuestion(m_config->questionArray[m_currQuestionIdx]); // Present the next question (if there is one) + if (m_currQuestionIdx < m_sessConfig->questionArray.size()) { // Double check we have a next question before launching the next question + m_app->presentQuestion(m_sessConfig->questionArray[m_currQuestionIdx]); // Present the next question (if there is one) } else { m_app->dialog.reset(); // Null the dialog pointer when all questions complete } } else { - m_app->presentQuestion(m_config->questionArray[m_currQuestionIdx]); // Relaunch the same dialog (this wasn't completed) + m_app->presentQuestion(m_sessConfig->questionArray[m_currQuestionIdx]); // Relaunch the same dialog (this wasn't completed) } } } else { // Write final session timestamp to log - if (notNull(logger) && m_config->logger.enable) { + if (notNull(logger) && m_sessConfig->logger.enable) { int totalTrials = 0; for (int tCount : m_completedTrials) { totalTrials += tCount; } logger->updateSessionEntry((m_remainingTrials[m_currTrialIdx] == 0), totalTrials); // Update session entry in database } - if (m_config->logger.enable) { + if (m_sessConfig->logger.enable) { endLogging(); } - m_app->markSessComplete(m_config->id); // Add this session to user's completed sessions + m_app->markSessComplete(m_sessConfig->id); // Add this session to user's completed sessions - m_feedbackMessage = formatFeedback(m_config->feedback.sessComplete); // Update the feedback message + m_feedbackMessage = formatFeedback(m_sessConfig->feedback.sessComplete); // Update the feedback message m_currQuestionIdx = -1; newState = PresentationState::sessionFeedback; } } else { // Block is complete but session isn't - m_feedbackMessage = formatFeedback(m_config->feedback.blockComplete); + m_feedbackMessage = formatFeedback(m_sessConfig->feedback.blockComplete); updateBlock(); newState = PresentationState::initial; } @@ -507,7 +509,7 @@ void Session::updatePresentationState() } else if (currentState == PresentationState::sessionFeedback) { if (m_hasSession) { - if (stateElapsedTime > m_config->timing.sessionFeedbackDuration && (!m_config->timing.sessionFeedbackRequireClick || !m_app->shootButtonUp)) { + if (stateElapsedTime > m_sessConfig->timing.sessionFeedbackDuration && (!m_sessConfig->timing.sessionFeedbackRequireClick || !m_app->shootButtonUp)) { newState = PresentationState::complete; // Save current user config and status @@ -518,15 +520,15 @@ void Session::updatePresentationState() Array remaining = m_app->updateSessionDropDown(); if (remaining.size() == 0) { - m_feedbackMessage = formatFeedback(m_config->feedback.allSessComplete); // Update the feedback message + m_feedbackMessage = formatFeedback(m_sessConfig->feedback.allSessComplete); // Update the feedback message moveOn = false; - if (m_app->experimentConfig.closeOnComplete || m_config->closeOnComplete) { + if (m_app->experimentConfig.closeOnComplete || m_sessConfig->closeOnComplete) { m_app->quitRequest(); } } else { - m_feedbackMessage = formatFeedback(m_config->feedback.sessComplete); // Update the feedback message - if (m_config->closeOnComplete) { + m_feedbackMessage = formatFeedback(m_sessConfig->feedback.sessComplete); // Update the feedback message + if (m_sessConfig->closeOnComplete) { m_app->quitRequest(); } moveOn = true; // Check for session complete (signal start of next session) @@ -553,30 +555,30 @@ void Session::updatePresentationState() { // handle state transition. m_timer.startTimer(); if (newState == PresentationState::trialTask) { - if (m_config->render.frameTimeMode == "restartwithtask") { + if (m_sessConfig->render.frameTimeMode == "restartwithtask") { m_frameTimeIdx = 0; // Reset the frame time index with the task if requested } m_taskStartTime = FPSciLogger::genUniqueTimestamp(); } currentState = newState; //If we switched to task, call initTargetAnimation to handle new trial - if ((newState == PresentationState::trialTask) || (newState == PresentationState::trialFeedback && hasNextCondition() && m_config->targetView.showRefTarget)) { - if (newState == PresentationState::trialTask && m_config->timing.maxPretrialAimDisplacement >= 0) { + if ((newState == PresentationState::trialTask) || (newState == PresentationState::trialFeedback && hasNextCondition() && m_trialConfig->targetView.showRefTarget)) { + if (newState == PresentationState::trialTask && m_trialConfig->timing.maxPretrialAimDisplacement >= 0) { // Test for aiming in valid region before spawning task targets Vector3 aim = m_camera->frame().lookVector().unit(); Vector3 ref = (m_lastRefTargetPos - m_camera->frame().translation).unit(); // Get the view displacement as the arccos of view/reference direction dot product (should never exceed 180 deg) float viewDisplacement = 180 / pif() * acosf(aim.dot(ref)); - if (viewDisplacement > m_config->timing.maxPretrialAimDisplacement) { + if (viewDisplacement > m_trialConfig->timing.maxPretrialAimDisplacement) { clearTargets(); // Clear targets (in case preview targets are being shown) - m_feedbackMessage = formatFeedback(m_config->feedback.aimInvalid); + m_feedbackMessage = formatFeedback(m_trialConfig->feedback.aimInvalid); currentState = PresentationState::trialFeedback; // Jump to feedback state w/ error message } } initTargetAnimation(); } - if (newState == PresentationState::pretrial && m_config->targetView.clearDecalsWithRef) { + if (newState == PresentationState::pretrial && m_trialConfig->targetView.clearDecalsWithRef) { m_weapon->clearDecals(); // Clear the decals when transitioning into the task state } } @@ -594,11 +596,11 @@ void Session::onSimulation(RealTime rdt, SimTime sdt, SimTime idt) void Session::recordTrialResponse(int destroyedTargets, int totalTargets) { - if (!m_config->logger.enable) return; // Skip this if the logger is disabled - if (m_config->logger.logTrialResponse) { + if (!m_sessConfig->logger.enable) return; // Skip this if the logger is disabled + if (m_trialConfig->logger.logTrialResponse) { // Trials table. Record trial start time, end time, and task completion time. FPSciLogger::TrialValues trialValues = { - "'" + m_config->id + "'", + "'" + m_sessConfig->id + "'", String(std::to_string(m_currTrialIdx)), String(std::to_string(m_completedTrials[m_currTrialIdx])), format("'Block %d'", m_currBlock), @@ -615,13 +617,13 @@ void Session::recordTrialResponse(int destroyedTargets, int totalTargets) void Session::accumulateTrajectories() { - if (notNull(logger) && m_config->logger.logTargetTrajectories) { + if (notNull(logger) && m_trialConfig->logger.logTargetTrajectories) { for (shared_ptr target : m_targetArray) { if (!target->isLogged()) continue; String name = target->name(); Point3 pos = target->frame().translation; TargetLocation location = TargetLocation(FPSciLogger::getFileTime(), name, currentState, pos); - if (m_config->logger.logOnChange) { + if (m_trialConfig->logger.logOnChange) { // Check for target in logged position table if (m_lastLogTargetLoc.containsKey(name) && location.noChangeFrom(m_lastLogTargetLoc[name])) { continue; // Duplicates last logged position/state (don't log) @@ -646,14 +648,14 @@ void Session::accumulatePlayerAction(PlayerActionType action, String targetName) static PlayerAction lastPA; - if (notNull(logger) && m_config->logger.logPlayerActions) { + if (notNull(logger) && m_trialConfig->logger.logPlayerActions) { BEGIN_PROFILER_EVENT("accumulatePlayerAction"); // recording target trajectories Point2 dir = getViewDirection(); Point3 loc = getPlayerLocation(); PlayerAction pa = PlayerAction(FPSciLogger::getFileTime(), dir, loc, currentState, action, targetName); // Check for log only on change condition - if (m_config->logger.logOnChange && pa.noChangeFrom(lastPA)) { + if (m_trialConfig->logger.logOnChange && pa.noChangeFrom(lastPA)) { return; // Early exit for (would be) duplicate log entry } logger->logPlayerAction(pa); @@ -663,7 +665,7 @@ void Session::accumulatePlayerAction(PlayerActionType action, String targetName) } void Session::accumulateFrameInfo(RealTime t, float sdt, float idt) { - if (notNull(logger) && m_config->logger.logFrameInfo) { + if (notNull(logger) && m_trialConfig->logger.logFrameInfo) { logger->logFrameInfo(FrameInfo(FPSciLogger::getFileTime(), sdt)); } } @@ -677,18 +679,18 @@ float Session::getElapsedTrialTime() { } float Session::getRemainingTrialTime() { - if (isNull(m_config)) return 10.0; - return m_config->timing.maxTrialDuration - m_timer.getTime(); + if (isNull(m_trialConfig)) return 10.0; + return m_trialConfig->timing.maxTrialDuration - m_timer.getTime(); } float Session::getProgress() { - if (notNull(m_config)) { + if (notNull(m_sessConfig)) { float remainingTrials = 0.f; for (int tcount : m_remainingTrials) { if (tcount < 0) return 0.f; // Infinite trials, never make any progress remainingTrials += (float)tcount; } - return 1.f - (remainingTrials / m_config->getTrialsPerBlock()); + return 1.f - (remainingTrials / m_sessConfig->getTrialsPerBlock()); } return fnan(); } @@ -760,7 +762,7 @@ String Session::formatFeedback(const String& input) { formatted = formatted.substr(0, foundIdx) + format("%d", m_currBlock) + formatted.substr(foundIdx + currBlock.length()); } else if (!formatted.compare(foundIdx, totalBlocks.length(), totalBlocks)) { - formatted = formatted.substr(0, foundIdx) + format("%d", m_config->blockCount) + formatted.substr(foundIdx + totalBlocks.length()); + formatted = formatted.substr(0, foundIdx) + format("%d", m_sessConfig->blockCount) + formatted.substr(foundIdx + totalBlocks.length()); } else if (!formatted.compare(foundIdx, trialTaskTimeMs.length(), trialTaskTimeMs)) { formatted = formatted.substr(0, foundIdx) + format("%d", (int)(m_taskExecutionTime * 1000)) + formatted.substr(foundIdx + trialTaskTimeMs.length()); @@ -842,7 +844,7 @@ shared_ptr Session::spawnReferenceTarget( { const int scaleIndex = clamp(iRound(log(size) / log(1.0f + TARGET_MODEL_ARRAY_SCALING) + TARGET_MODEL_ARRAY_OFFSET), 0, TARGET_MODEL_SCALE_COUNT - 1); - String refId = m_config->id + "_reference"; + String refId = m_sessConfig->id + "_reference"; if (isNull(m_targetModels->getPointer(refId))) { // This session doesn't have a custom reference target refId = "reference"; diff --git a/source/Session.h b/source/Session.h index 900a193c..4ff5785b 100644 --- a/source/Session.h +++ b/source/Session.h @@ -171,7 +171,8 @@ class Session : public ReferenceCountedObject { FPSciApp* m_app = nullptr; ///< Pointer to the app Scene* m_scene = nullptr; ///< Pointer to the scene - shared_ptr m_config; ///< The session this experiment will run + shared_ptr m_sessConfig; ///< Configuration for this session + shared_ptr m_trialConfig; ///< Configuration for the trial we are in String m_dbFilename; ///< Filename for output logging (less the .db extension) @@ -233,7 +234,7 @@ class Session : public ReferenceCountedObject { inline void runTrialCommands(String evt) { evt = toLower(evt); - auto cmds = (evt == "start") ? m_config->commands.trialStartCmds : m_config->commands.trialEndCmds; + auto cmds = (evt == "start") ? m_sessConfig->commands.trialStartCmds : m_sessConfig->commands.trialEndCmds; for (auto cmd : cmds) { m_trialProcesses.append(runCommand(cmd, evt + " of trial")); } @@ -248,7 +249,7 @@ class Session : public ReferenceCountedObject { inline void runSessionCommands(String evt) { evt = toLower(evt); - auto cmds = (evt == "start") ? m_config->commands.sessionStartCmds : m_config->commands.sessionEndCmds; + auto cmds = (evt == "start") ? m_sessConfig->commands.sessionStartCmds : m_sessConfig->commands.sessionEndCmds; for (auto cmd : cmds) { m_sessProcesses.append(runCommand(cmd, evt + " of session")); } From 63f92165e80b8a17371f20c0e0339c4b94fb1969 Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Fri, 5 Aug 2022 13:16:08 -0400 Subject: [PATCH 03/38] Fix for Session::updateBlock() --- source/Session.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/Session.cpp b/source/Session.cpp index 130a6fc0..b7b75581 100644 --- a/source/Session.cpp +++ b/source/Session.cpp @@ -173,6 +173,8 @@ bool Session::nextCondition() { if (unrunTrialIdxs.size() == 0) return false; int idx = Random::common().integer(0, unrunTrialIdxs.size()-1); m_currTrialIdx = unrunTrialIdxs[idx]; + + // Get and update the trial configuration m_trialConfig = shared_ptr(&(m_sessConfig->trials[m_currTrialIdx])); m_app->updateTrial(m_trialConfig); @@ -196,7 +198,7 @@ bool Session::updateBlock(bool init) { const Array>& targets = m_trials[i]; if (init) { // If this is the first block in the session m_completedTrials.append(0); // This increments across blocks (unique trial index) - m_remainingTrials.append(m_trialConfig->count); // This is reset across blocks (tracks progress) + m_remainingTrials.append(m_sessConfig->trials[i].count); // This is reset across blocks (tracks progress) m_targetConfigs.append(targets); // This only needs to be setup once } else { // Update for a new block in the session From f738e0afe30c5e1f56d94fd899ec3085cc8d1896 Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Fri, 5 Aug 2022 13:18:05 -0400 Subject: [PATCH 04/38] Minor signature change for FPSciApp::updateTrial() --- source/FPSciApp.cpp | 2 +- source/FPSciApp.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/FPSciApp.cpp b/source/FPSciApp.cpp index ad2b1480..66af1111 100644 --- a/source/FPSciApp.cpp +++ b/source/FPSciApp.cpp @@ -698,7 +698,7 @@ void FPSciApp::updateSession(const String& id, const bool forceSceneReload) { } } -void FPSciApp::updateTrial(const shared_ptr config, bool forceSceneReload) { +void FPSciApp::updateTrial(const shared_ptr config, const bool forceSceneReload) { trialConfig = config; // Naive way to store trial config pointer for now updateConfigParameters(config, forceSceneReload); } diff --git a/source/FPSciApp.h b/source/FPSciApp.h index a60b168c..bae28ef5 100644 --- a/source/FPSciApp.h +++ b/source/FPSciApp.h @@ -255,7 +255,7 @@ class FPSciApp : public GApp { void markSessComplete(String id); /** Updates experiment state to the provided session id and updates player parameters (including mouse sensitivity) */ virtual void updateSession(const String& id, bool forceSceneReload = false); - void updateTrial(const shared_ptr config, bool forceSceneReload = false); + void updateTrial(const shared_ptr config, const bool forceSceneReload = false); void updateConfigParameters(const shared_ptr config, bool forceSceneReload = false); void updateFrameParameters(int frameDelay, float frameRate); void updateTargetColor(const shared_ptr& target); From caac43dad1a87aa42ae0dc05bd7cb26f9ddaf6b0 Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Mon, 8 Aug 2022 18:14:56 -0400 Subject: [PATCH 05/38] Fix TrialConfig shared pointer allocation --- source/Session.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Session.cpp b/source/Session.cpp index b7b75581..311d6e95 100644 --- a/source/Session.cpp +++ b/source/Session.cpp @@ -175,7 +175,7 @@ bool Session::nextCondition() { m_currTrialIdx = unrunTrialIdxs[idx]; // Get and update the trial configuration - m_trialConfig = shared_ptr(&(m_sessConfig->trials[m_currTrialIdx])); + m_trialConfig = TrialConfig::createShared(m_sessConfig->trials[m_currTrialIdx]); m_app->updateTrial(m_trialConfig); // Produce (potentially random in range) pretrial duration From de70a989fa2deaf011297858cf3a913a87da6f2d Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Mon, 8 Aug 2022 18:23:34 -0400 Subject: [PATCH 06/38] Move session target clear from param update --- source/FPSciApp.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/FPSciApp.cpp b/source/FPSciApp.cpp index 66af1111..0e1a7426 100644 --- a/source/FPSciApp.cpp +++ b/source/FPSciApp.cpp @@ -647,6 +647,9 @@ void FPSciApp::updateSession(const String& id, const bool forceSceneReload) { materials.set(id, makeMaterials(tconfig)); } + // Handle clearing the targets here (clear any remaining targets before loading a new scene) + if (notNull(scene())) sess->clearTargets(); + // Update the application w/ the session parameters updateConfigParameters(sessConfig, forceSceneReload); @@ -743,9 +746,6 @@ void FPSciApp::updateConfigParameters(const shared_ptr config, const hudFont = GFont::fromFile(System::findDataFile(config->hud.hudFont)); m_combatFont = GFont::fromFile(System::findDataFile(config->targetView.combatTextFont)); - // Handle clearing the targets here (clear any remaining targets before loading a new scene) - if (notNull(scene())) sess->clearTargets(); - // Load the experiment scene if we haven't already (target only) if (config->scene.name.empty()) { // No scene specified, load default scene From e23d8fa6b4b6eb33f02cad389313b5bcb95b465d Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Tue, 9 Aug 2022 14:45:57 -0400 Subject: [PATCH 07/38] Add new referenceTarget state to session --- source/FPSciApp.cpp | 2 +- source/FPSciApp.h | 3 ++- source/Session.cpp | 48 ++++++++++++++++++++++++++------------------- source/Session.h | 4 ++-- 4 files changed, 33 insertions(+), 24 deletions(-) diff --git a/source/FPSciApp.cpp b/source/FPSciApp.cpp index 0e1a7426..168d952b 100644 --- a/source/FPSciApp.cpp +++ b/source/FPSciApp.cpp @@ -1379,7 +1379,7 @@ void FPSciApp::onUserInput(UserInput* ui) { } for (GKey dummyShoot : keyMap.map["dummyShoot"]) { - if (ui->keyPressed(dummyShoot) && (sess->currentState == PresentationState::trialFeedback) && !m_userSettingsWindow->visible()) { + if (ui->keyPressed(dummyShoot) && (sess->currentState == PresentationState::refTarget) && !m_userSettingsWindow->visible()) { Array> dontHit; dontHit.append(m_explosions); dontHit.append(sess->unhittableTargets()); diff --git a/source/FPSciApp.h b/source/FPSciApp.h index bae28ef5..c6720322 100644 --- a/source/FPSciApp.h +++ b/source/FPSciApp.h @@ -33,11 +33,12 @@ class WaypointManager; // ready: ready scene that happens before beginning of a task. // task: actual task (e.g. instant hit, tracking, projectile, ...) // feedback: feedback showing whether task performance was successful or not. -enum PresentationState { initial, pretrial, trialTask, trialFeedback, sessionFeedback, complete }; +enum PresentationState { initial, refTarget, pretrial, trialTask, trialFeedback, sessionFeedback, complete }; static String presentationStateToString(const PresentationState& state) { String stateStr = "N/A"; switch (state) { case initial: stateStr = "initial"; break; + case refTarget: stateStr = "referenceTarget"; break; case pretrial: stateStr = "pretrial"; break; case trialTask: stateStr = "trialTask"; break; case trialFeedback: stateStr = "trialFeedback"; break; diff --git a/source/Session.cpp b/source/Session.cpp index 311d6e95..ee46c8c9 100644 --- a/source/Session.cpp +++ b/source/Session.cpp @@ -265,13 +265,13 @@ void Session::randomizePosition(const shared_ptr& target) const { target->setFrame(loc); } -void Session::initTargetAnimation() { +void Session::initTargetAnimation(const bool task) { // initialize target location based on the initial displacement values // Not reference: we don't want it to change after the first call. const Point3 initialSpawnPos = m_player->getCameraFrame().translation; // In task state, spawn a test target. Otherwise spawn a target at straight ahead. - if (currentState == PresentationState::trialTask) { + if (task) { if (m_trialConfig->targetView.previewWithRef && m_trialConfig->targetView.showRefTarget) { // Activate the preview targets for (shared_ptr target : m_targetArray) { @@ -404,8 +404,15 @@ void Session::updatePresentationState() m_player->setMoveEnable(false); } if (!(m_app->shootButtonUp && m_trialConfig->timing.clickToStart)) { - newState = PresentationState::trialFeedback; + newState = PresentationState::referenceTarget; + } + } + else if (currentState == PresentationState::referenceTarget) { + // State for showing the trial reference target + if (remainingTargets == 0) { + newState = PresentationState::pretrial; } + } else if (currentState == PresentationState::pretrial) { @@ -448,7 +455,7 @@ void Session::updatePresentationState() } else if (currentState == PresentationState::trialFeedback) { - if ((stateElapsedTime > m_trialConfig->timing.trialFeedbackDuration) && (remainingTargets <= 0)) + if (stateElapsedTime > m_trialConfig->timing.trialFeedbackDuration) { if (blockComplete()) { m_currBlock++; // Increment the block index @@ -505,7 +512,7 @@ void Session::updatePresentationState() else { m_feedbackMessage = ""; // Clear the feedback message nextCondition(); - newState = PresentationState::pretrial; + newState = PresentationState::referenceTarget; } } } @@ -548,7 +555,6 @@ void Session::updatePresentationState() moveOn = false; } } - else { newState = currentState; } @@ -556,17 +562,21 @@ void Session::updatePresentationState() if (currentState != newState) { // handle state transition. m_timer.startTimer(); - if (newState == PresentationState::trialTask) { + if (newState == PresentationState::referenceTarget) { + initTargetAnimation(false); // Spawn the reference (and also preview if requested) target(s) + } + else if (newState == PresentationState::pretrial) { + // Clear weapon miss decals (if requested) + if (m_trialConfig->targetView.clearDecalsWithRef) { + m_weapon->clearDecals(); + } + } + else if (newState == PresentationState::trialTask) { if (m_sessConfig->render.frameTimeMode == "restartwithtask") { m_frameTimeIdx = 0; // Reset the frame time index with the task if requested } - m_taskStartTime = FPSciLogger::genUniqueTimestamp(); - } - currentState = newState; - //If we switched to task, call initTargetAnimation to handle new trial - if ((newState == PresentationState::trialTask) || (newState == PresentationState::trialFeedback && hasNextCondition() && m_trialConfig->targetView.showRefTarget)) { - if (newState == PresentationState::trialTask && m_trialConfig->timing.maxPretrialAimDisplacement >= 0) { - // Test for aiming in valid region before spawning task targets + // Test for aiming in valid region before spawning task targets + if (m_trialConfig->timing.maxPretrialAimDisplacement >= 0) { Vector3 aim = m_camera->frame().lookVector().unit(); Vector3 ref = (m_lastRefTargetPos - m_camera->frame().translation).unit(); // Get the view displacement as the arccos of view/reference direction dot product (should never exceed 180 deg) @@ -574,15 +584,13 @@ void Session::updatePresentationState() if (viewDisplacement > m_trialConfig->timing.maxPretrialAimDisplacement) { clearTargets(); // Clear targets (in case preview targets are being shown) m_feedbackMessage = formatFeedback(m_trialConfig->feedback.aimInvalid); - currentState = PresentationState::trialFeedback; // Jump to feedback state w/ error message + newState = PresentationState::trialFeedback; // Jump to feedback state w/ error message } } - initTargetAnimation(); - } - - if (newState == PresentationState::pretrial && m_trialConfig->targetView.clearDecalsWithRef) { - m_weapon->clearDecals(); // Clear the decals when transitioning into the task state + m_taskStartTime = FPSciLogger::genUniqueTimestamp(); + initTargetAnimation(true); // Spawn task targets (or convert from previews) } + currentState = newState; } } diff --git a/source/Session.h b/source/Session.h index 4ff5785b..6163f693 100644 --- a/source/Session.h +++ b/source/Session.h @@ -183,7 +183,7 @@ class Session : public ReferenceCountedObject { // Experiment management int m_destroyedTargets = 0; ///< Number of destroyed target int m_hitCount = 0; ///< Count of total hits in this trial - bool m_hasSession; ///< Flag indicating whether psych helper has loaded a valid session + bool m_hasSession; ///< Flag indicating whether this session was loaded from a valid config int m_currBlock = 1; ///< Index to the current block of trials Array>> m_trials; ///< Storage for trials (to repeat over blocks) String m_feedbackMessage; ///< Message to show when trial complete @@ -392,7 +392,7 @@ class Session : public ReferenceCountedObject { } void randomizePosition(const shared_ptr& target) const; - void initTargetAnimation(); + void initTargetAnimation(const bool task); void spawnTrialTargets(Point3 initialSpawnPos, bool previewMode = false); bool blockComplete() const; From 7bc253367a198804090b9f2cfc72124c6079fc9c Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Wed, 10 Aug 2022 11:07:14 -0400 Subject: [PATCH 08/38] Update state name and fix config for initPlayer() --- source/FPSciApp.cpp | 8 ++++---- source/FPSciApp.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/source/FPSciApp.cpp b/source/FPSciApp.cpp index 168d952b..13a8f00c 100644 --- a/source/FPSciApp.cpp +++ b/source/FPSciApp.cpp @@ -539,9 +539,9 @@ void FPSciApp::initPlayer(const shared_ptr config, bool setSpawnPosit // Set gravity and camera field of view Vector3 grav = experimentConfig.player.gravity; float FoV = experimentConfig.render.hFoV; - if (notNull(sessConfig)) { - grav = sessConfig->player.gravity; - FoV = sessConfig->render.hFoV; + if (notNull(config)) { + grav = config->player.gravity; + FoV = config->render.hFoV; } pscene->setGravity(grav); @@ -1379,7 +1379,7 @@ void FPSciApp::onUserInput(UserInput* ui) { } for (GKey dummyShoot : keyMap.map["dummyShoot"]) { - if (ui->keyPressed(dummyShoot) && (sess->currentState == PresentationState::refTarget) && !m_userSettingsWindow->visible()) { + if (ui->keyPressed(dummyShoot) && (sess->currentState == PresentationState::referenceTarget) && !m_userSettingsWindow->visible()) { Array> dontHit; dontHit.append(m_explosions); dontHit.append(sess->unhittableTargets()); diff --git a/source/FPSciApp.h b/source/FPSciApp.h index c6720322..50421b2d 100644 --- a/source/FPSciApp.h +++ b/source/FPSciApp.h @@ -33,12 +33,12 @@ class WaypointManager; // ready: ready scene that happens before beginning of a task. // task: actual task (e.g. instant hit, tracking, projectile, ...) // feedback: feedback showing whether task performance was successful or not. -enum PresentationState { initial, refTarget, pretrial, trialTask, trialFeedback, sessionFeedback, complete }; +enum PresentationState { initial, referenceTarget, pretrial, trialTask, trialFeedback, sessionFeedback, complete }; static String presentationStateToString(const PresentationState& state) { String stateStr = "N/A"; switch (state) { case initial: stateStr = "initial"; break; - case refTarget: stateStr = "referenceTarget"; break; + case referenceTarget: stateStr = "referenceTarget"; break; case pretrial: stateStr = "pretrial"; break; case trialTask: stateStr = "trialTask"; break; case trialFeedback: stateStr = "trialFeedback"; break; From 1809d886dabca5aadc68d33b3d2d0345541210c2 Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Wed, 10 Aug 2022 11:07:25 -0400 Subject: [PATCH 09/38] Move FPSciGraphics to using trialConfig --- source/FPSciGraphics.cpp | 136 +++++++++++++++++++-------------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/source/FPSciGraphics.cpp b/source/FPSciGraphics.cpp index 468aeae2..80585e3b 100644 --- a/source/FPSciGraphics.cpp +++ b/source/FPSciGraphics.cpp @@ -111,17 +111,17 @@ void FPSciApp::onGraphics3D(RenderDevice* rd, Array >& surfa rd->copyTextureFromScreen(m_ldrBufferPrecomposite->texture(0), rd->viewport()); // Resample the copied framebuffer onto the (controlled resolution) composite shader input buffer rd->push2D(m_ldrBufferComposite); { - Draw::rect2D(rd->viewport(), rd, Color3::white(), m_ldrBufferPrecomposite->texture(0), sessConfig->render.samplerPrecomposite, true); + Draw::rect2D(rd->viewport(), rd, Color3::white(), m_ldrBufferPrecomposite->texture(0), trialConfig->render.samplerPrecomposite, true); } rd->pop2D(); } } void FPSciApp::onPostProcessHDR3DEffects(RenderDevice* rd) { if (notNull(m_hdrShader3DOutput)) { - if(sessConfig->render.shader3D.empty()) { + if(trialConfig->render.shader3D.empty()) { // No shader specified, just a resize perform pass through into 3D output buffer from framebuffer rd->push2D(m_hdrShader3DOutput); { - Draw::rect2D(rd->viewport(), rd, Color3::white(), m_framebuffer->texture(0), sessConfig->render.sampler3D); + Draw::rect2D(rd->viewport(), rd, Color3::white(), m_framebuffer->texture(0), trialConfig->render.sampler3D); } rd->pop2D(); } else { @@ -130,14 +130,14 @@ void FPSciApp::onPostProcessHDR3DEffects(RenderDevice* rd) { rd->push2D(m_hdrShader3DOutput); { // Setup shadertoy-style args Args args; - args.setUniform("iChannel0", m_framebuffer->texture(0), sessConfig->render.sampler3D); + args.setUniform("iChannel0", m_framebuffer->texture(0), trialConfig->render.sampler3D); const float iTime = float(System::time() - m_startTime); args.setUniform("iTime", iTime); args.setUniform("iTimeDelta", iTime - m_lastTime); args.setUniform("iMouse", userInput->mouseXY()); args.setUniform("iFrame", m_frameNumber); args.setRect(rd->viewport()); - LAUNCH_SHADER_PTR(m_shaderTable[sessConfig->render.shader3D], args); + LAUNCH_SHADER_PTR(m_shaderTable[trialConfig->render.shader3D], args); m_lastTime = iTime; } rd->pop2D(); @@ -146,7 +146,7 @@ void FPSciApp::onPostProcessHDR3DEffects(RenderDevice* rd) { // Resample the shader output buffer into the framebuffer rd->push2D(); { - Draw::rect2D(rd->viewport(), rd, Color3::white(), m_hdrShader3DOutput->texture(0), sessConfig->render.sampler3DOutput); + Draw::rect2D(rd->viewport(), rd, Color3::white(), m_hdrShader3DOutput->texture(0), trialConfig->render.sampler3DOutput); } rd->pop2D(); } @@ -165,7 +165,7 @@ void FPSciApp::onGraphics2D(RenderDevice* rd, Array>& pose } rd->pop2D(); if(notNull(m_ldrBuffer2D)){ - if (sessConfig->render.shader2D.empty()) { + if (trialConfig->render.shader2D.empty()) { m_ldrShader2DOutput = m_ldrBuffer2D; // Redirect output pointer to input (skip shading) } else { @@ -173,14 +173,14 @@ void FPSciApp::onGraphics2D(RenderDevice* rd, Array>& pose rd->push2D(m_ldrShader2DOutput); { // Setup shadertoy-style args Args args; - args.setUniform("iChannel0", m_ldrBuffer2D->texture(0), sessConfig->render.sampler2D); + args.setUniform("iChannel0", m_ldrBuffer2D->texture(0), trialConfig->render.sampler2D); const float iTime = float(System::time() - m_startTime); args.setUniform("iTime", iTime); args.setUniform("iTimeDelta", iTime - m_last2DTime); args.setUniform("iMouse", userInput->mouseXY()); args.setUniform("iFrame", m_frameNumber); args.setRect(rd->viewport()); - LAUNCH_SHADER_PTR(m_shaderTable[sessConfig->render.shader2D], args); + LAUNCH_SHADER_PTR(m_shaderTable[trialConfig->render.shader2D], args); m_last2DTime = iTime; } rd->pop2D(); END_PROFILER_EVENT(); @@ -189,13 +189,13 @@ void FPSciApp::onGraphics2D(RenderDevice* rd, Array>& pose // Direct shader output to the display or composite shader input (if specified) isNull(m_ldrBufferComposite) ? rd->push2D() : rd->push2D(m_ldrBufferComposite); { rd->setBlendFunc(RenderDevice::BLEND_SRC_ALPHA, RenderDevice::BLEND_ONE_MINUS_SRC_ALPHA); - Draw::rect2D(rd->viewport(), rd, Color3::white(), m_ldrShader2DOutput->texture(0), sessConfig->render.sampler2DOutput); + Draw::rect2D(rd->viewport(), rd, Color3::white(), m_ldrShader2DOutput->texture(0), trialConfig->render.sampler2DOutput); } rd->pop2D(); } // Handle post-2D composite shader here if (m_ldrBufferComposite) { - if (sessConfig->render.shaderComposite.empty()) { + if (trialConfig->render.shaderComposite.empty()) { m_ldrShaderCompositeOutput = m_ldrBufferComposite; // Redirect output pointer to input } else { @@ -205,14 +205,14 @@ void FPSciApp::onGraphics2D(RenderDevice* rd, Array>& pose rd->push2D(m_ldrShaderCompositeOutput); { // Setup shadertoy-style args Args args; - args.setUniform("iChannel0", m_ldrBufferComposite->texture(0), sessConfig->render.samplerComposite); + args.setUniform("iChannel0", m_ldrBufferComposite->texture(0), trialConfig->render.samplerComposite); const float iTime = float(System::time() - m_startTime); args.setUniform("iTime", iTime); args.setUniform("iTimeDelta", iTime - m_lastCompositeTime); args.setUniform("iMouse", userInput->mouseXY()); args.setUniform("iFrame", m_frameNumber); args.setRect(rd->viewport()); - LAUNCH_SHADER_PTR(m_shaderTable[sessConfig->render.shaderComposite], args); + LAUNCH_SHADER_PTR(m_shaderTable[trialConfig->render.shaderComposite], args); m_lastCompositeTime = iTime; } rd->pop2D(); @@ -221,7 +221,7 @@ void FPSciApp::onGraphics2D(RenderDevice* rd, Array>& pose // Copy the shader output buffer into the framebuffer rd->push2D(); { - Draw::rect2D(rd->viewport(), rd, Color3::white(), m_ldrShaderCompositeOutput->texture(0), sessConfig->render.samplerFinal); + Draw::rect2D(rd->viewport(), rd, Color3::white(), m_ldrShaderCompositeOutput->texture(0), trialConfig->render.samplerFinal); } rd->pop2D(); } @@ -278,8 +278,8 @@ void FPSciApp::draw2DElements(RenderDevice* rd, Vector2 resolution) { } // Click-to-photon mouse event indicator - if (sessConfig->clickToPhoton.enabled && sessConfig->clickToPhoton.mode != "total") { - drawClickIndicator(rd, sessConfig->clickToPhoton.mode, resolution); + if (trialConfig->clickToPhoton.enabled && trialConfig->clickToPhoton.mode != "total") { + drawClickIndicator(rd, trialConfig->clickToPhoton.mode, resolution); } // Player camera only indicators @@ -297,19 +297,19 @@ void FPSciApp::drawDelayed2DElements(RenderDevice* rd, Vector2 resolution) { const float scale = resolution.x / 1920.0f; // Draw target health bars - if (sessConfig->targetView.showHealthBars) { + if (trialConfig->targetView.showHealthBars) { for (auto const& target : sess->targetArray()) { target->drawHealthBar(rd, *activeCamera(), *m_framebuffer, - sessConfig->targetView.healthBarSize, - sessConfig->targetView.healthBarOffset, - sessConfig->targetView.healthBarBorderSize, - sessConfig->targetView.healthBarColors, - sessConfig->targetView.healthBarBorderColor); + trialConfig->targetView.healthBarSize, + trialConfig->targetView.healthBarOffset, + trialConfig->targetView.healthBarBorderSize, + trialConfig->targetView.healthBarColors, + trialConfig->targetView.healthBarBorderColor); } } // Draw the combat text - if (sessConfig->targetView.showCombatText) { + if (trialConfig->targetView.showCombatText) { Array toRemove; for (int i = 0; i < m_combatTextList.size(); i++) { bool remove = !m_combatTextList[i]->draw(rd, *playerCamera, *m_framebuffer); @@ -319,36 +319,36 @@ void FPSciApp::drawDelayed2DElements(RenderDevice* rd, Vector2 resolution) { m_combatTextList.removeNulls(); } - if (sessConfig->clickToPhoton.enabled && sessConfig->clickToPhoton.mode == "total") { + if (trialConfig->clickToPhoton.enabled && trialConfig->clickToPhoton.mode == "total") { drawClickIndicator(rd, "total", resolution); } // Draw the HUD here - if (sessConfig->hud.enable) { + if (trialConfig->hud.enable) { drawHUD(rd, resolution); } } void FPSciApp::drawClickIndicator(RenderDevice* rd, String mode, Vector2 resolution) { // Click to photon latency measuring corner box - if (sessConfig->clickToPhoton.enabled) { + if (trialConfig->clickToPhoton.enabled) { float boxLeft = 0.0f; // Paint both sides by the width of latency measuring box. - Point2 latencyRect = sessConfig->clickToPhoton.size * resolution; - float boxTop = resolution.y * sessConfig->clickToPhoton.vertPos - latencyRect.y / 2; - if (sessConfig->clickToPhoton.mode == "both") { + Point2 latencyRect = trialConfig->clickToPhoton.size * resolution; + float boxTop = resolution.y * trialConfig->clickToPhoton.vertPos - latencyRect.y / 2; + if (trialConfig->clickToPhoton.mode == "both") { boxTop = (mode == "minimum") ? boxTop - latencyRect.y : boxTop + latencyRect.y; } - if (sessConfig->clickToPhoton.side == "right") { + if (trialConfig->clickToPhoton.side == "right") { boxLeft = resolution.x - latencyRect.x; } // Draw the "active" box Color3 boxColor; - if (sessConfig->clickToPhoton.mode == "frameRate") { - boxColor = (frameToggle) ? sessConfig->clickToPhoton.colors[0] : sessConfig->clickToPhoton.colors[1]; + if (trialConfig->clickToPhoton.mode == "frameRate") { + boxColor = (frameToggle) ? trialConfig->clickToPhoton.colors[0] : trialConfig->clickToPhoton.colors[1]; frameToggle = !frameToggle; } - else boxColor = (shootButtonUp) ? sessConfig->clickToPhoton.colors[0] : sessConfig->clickToPhoton.colors[1]; + else boxColor = (shootButtonUp) ? trialConfig->clickToPhoton.colors[0] : trialConfig->clickToPhoton.colors[1]; Draw::rect2D(Rect2D::xywh(boxLeft, boxTop, latencyRect.x, latencyRect.y), rd, boxColor); } } @@ -362,7 +362,7 @@ void FPSciApp::drawFeedbackMessage(RenderDevice* rd) { const float scale = rd->width() / 1920.f; String message = sess->getFeedbackMessage(); const float centerHeight = rd->height() * 0.4f; - const float scaledFontSize = floor(sessConfig->feedback.fontSize * scale); + const float scaledFontSize = floor(trialConfig->feedback.fontSize * scale); if (!message.empty()) { String currLine; Array lines = stringSplit(message, '\n'); @@ -372,13 +372,13 @@ void FPSciApp::drawFeedbackMessage(RenderDevice* rd) { vertPos - 1.5f * scaledFontSize, (float) rd->width(), scaledFontSize * (lines.length() + 1) * 1.5f), - rd, sessConfig->feedback.backgroundColor); + rd, trialConfig->feedback.backgroundColor); for (String line : lines) { outputFont->draw2D(rd, line.c_str(), (Point2(rd->width() * 0.5f, vertPos)).floor(), scaledFontSize, - sessConfig->feedback.color, - sessConfig->feedback.outlineColor, + trialConfig->feedback.color, + trialConfig->feedback.outlineColor, GFont::XALIGN_CENTER, GFont::YALIGN_CENTER ); vertPos += scaledFontSize * 1.5f; @@ -427,28 +427,28 @@ void FPSciApp::drawHUD(RenderDevice *rd, Vector2 resolution) { RealTime now = m_lastOnSimulationRealTime; // Weapon ready status (cooldown indicator) - if (sessConfig->hud.renderWeaponStatus) { + if (trialConfig->hud.renderWeaponStatus) { // Draw the "active" cooldown box - if (sessConfig->hud.cooldownMode == "box") { + if (trialConfig->hud.cooldownMode == "box") { float boxLeft = 0.0f; - if (sessConfig->hud.weaponStatusSide == "right") { + if (trialConfig->hud.weaponStatusSide == "right") { // swap side - boxLeft = resolution.x * (1.0f - sessConfig->clickToPhoton.size.x); + boxLeft = resolution.x * (1.0f - trialConfig->clickToPhoton.size.x); } Draw::rect2D( Rect2D::xywh( boxLeft, resolution.y * (weapon->cooldownRatio(now)), - resolution.x * sessConfig->clickToPhoton.size.x, + resolution.x * trialConfig->clickToPhoton.size.x, resolution.y * (1.0f - weapon->cooldownRatio(now)) ), rd, Color3::white() * 0.8f ); } - else if (sessConfig->hud.cooldownMode == "ring") { + else if (trialConfig->hud.cooldownMode == "ring") { // Draw cooldown "ring" instead of box - const float iRad = sessConfig->hud.cooldownInnerRadius; - const float oRad = iRad + sessConfig->hud.cooldownThickness; - const int segments = sessConfig->hud.cooldownSubdivisions; + const float iRad = trialConfig->hud.cooldownInnerRadius; + const float oRad = iRad + trialConfig->hud.cooldownThickness; + const int segments = trialConfig->hud.cooldownSubdivisions; int segsToLight = static_cast(ceilf((1 - weapon->cooldownRatio(now))*segments)); // Create the segments for (int i = 0; i < segsToLight; i++) { @@ -461,52 +461,52 @@ void FPSciApp::drawHUD(RenderDevice *rd, Vector2 resolution) { center + Vector2(iRad*sin(theta + inc), -iRad * cos(theta + inc)), center + Vector2(iRad*sin(theta), -iRad * cos(theta)) }; - Draw::poly2D(verts, rd, sessConfig->hud.cooldownColor); + Draw::poly2D(verts, rd, trialConfig->hud.cooldownColor); } } } // Draw the player health bar - if (sessConfig->hud.showPlayerHealthBar) { + if (trialConfig->hud.showPlayerHealthBar) { //const float guardband = (rd->framebuffer()->width() - window()->framebuffer()->width()) / 2.0f; const float health = scene()->typedEntity("player")->health(); - Point2 location = sessConfig->hud.playerHealthBarPos * resolution; + Point2 location = trialConfig->hud.playerHealthBarPos * resolution; location.y += (m_debugMenuHeight * scale.y); - const Point2 size = sessConfig->hud.playerHealthBarSize * resolution; - const Vector2 border = sessConfig->hud.playerHealthBarBorderSize * resolution; - const Color4 borderColor = sessConfig->hud.playerHealthBarBorderColor; - const Color4 color = sessConfig->hud.playerHealthBarColors[1] * (1.0f - health) + sessConfig->hud.playerHealthBarColors[0] * health; + const Point2 size = trialConfig->hud.playerHealthBarSize * resolution; + const Vector2 border = trialConfig->hud.playerHealthBarBorderSize * resolution; + const Color4 borderColor = trialConfig->hud.playerHealthBarBorderColor; + const Color4 color = trialConfig->hud.playerHealthBarColors[1] * (1.0f - health) + trialConfig->hud.playerHealthBarColors[0] * health; Draw::rect2D(Rect2D::xywh(location - border, size + border + border), rd, borderColor); Draw::rect2D(Rect2D::xywh(location, size*Point2(health, 1.0f)), rd, color); } // Draw the ammo indicator - if (sessConfig->hud.showAmmo) { + if (trialConfig->hud.showAmmo) { //const float guardband = (rd->framebuffer()->width() - window()->framebuffer()->width()) / 2.0f; Point2 lowerRight = resolution; //Point2(static_cast(rd->viewport().width()), static_cast(rd->viewport().height())) - Point2(guardband, guardband); hudFont->draw2D(rd, - format("%d/%d", weapon->remainingAmmo(), sessConfig->weapon.maxAmmo), - lowerRight - sessConfig->hud.ammoPosition, - sessConfig->hud.ammoSize, - sessConfig->hud.ammoColor, - sessConfig->hud.ammoOutlineColor, + format("%d/%d", weapon->remainingAmmo(), trialConfig->weapon.maxAmmo), + lowerRight - trialConfig->hud.ammoPosition, + trialConfig->hud.ammoSize, + trialConfig->hud.ammoColor, + trialConfig->hud.ammoOutlineColor, GFont::XALIGN_RIGHT, GFont::YALIGN_BOTTOM ); } - if (sessConfig->hud.showBanner) { + if (trialConfig->hud.showBanner) { const shared_ptr scoreBannerTexture = hudTextures["scoreBannerBackdrop"]; - const Point2 hudCenter(resolution.x / 2.0f, sessConfig->hud.bannerVertVisible * scoreBannerTexture->height() * scale.y + debugMenuHeight()); + const Point2 hudCenter(resolution.x / 2.0f, trialConfig->hud.bannerVertVisible * scoreBannerTexture->height() * scale.y + debugMenuHeight()); Draw::rect2D((scoreBannerTexture->rect2DBounds() * scale - scoreBannerTexture->vector2Bounds() * scale / 2.0f) * 0.8f + hudCenter, rd, Color3::white(), scoreBannerTexture); // Create strings for time remaining, progress in sessions, and score float time; - if (sessConfig->hud.bannerTimerMode == "remaining") { + if (trialConfig->hud.bannerTimerMode == "remaining") { time = sess->getRemainingTrialTime(); if (time < 0.f) time = 0.f; } - else if (sessConfig->hud.bannerTimerMode == "elapsed") { + else if (trialConfig->hud.bannerTimerMode == "elapsed") { time = sess->getElapsedTrialTime(); } String time_string = time < 10000.f ? format("%0.1f", time) : "---"; // Only allow up to 3 digit time strings @@ -532,16 +532,16 @@ void FPSciApp::drawHUD(RenderDevice *rd, Vector2 resolution) { score_string = format("%dB", (int)G3D::round(score / 1e9)); } - if (sessConfig->hud.bannerTimerMode != "none" && sess->inTask()) { - hudFont->draw2D(rd, time_string, hudCenter - Vector2(80, 0) * scale.x, scale.x * sessConfig->hud.bannerSmallFontSize, + if (trialConfig->hud.bannerTimerMode != "none" && sess->inTask()) { + hudFont->draw2D(rd, time_string, hudCenter - Vector2(80, 0) * scale.x, scale.x * trialConfig->hud.bannerSmallFontSize, Color3::white(), Color4::clear(), GFont::XALIGN_RIGHT, GFont::YALIGN_CENTER); } - if(sessConfig->hud.bannerShowProgress) hudFont->draw2D(rd, prog_string, hudCenter + Vector2(0, -1), scale.x * sessConfig->hud.bannerLargeFontSize, Color3::white(), Color4::clear(), GFont::XALIGN_CENTER, GFont::YALIGN_CENTER); - if(sessConfig->hud.bannerShowScore) hudFont->draw2D(rd, score_string, hudCenter + Vector2(125, 0) * scale, scale.x * sessConfig->hud.bannerSmallFontSize, Color3::white(), Color4::clear(), GFont::XALIGN_RIGHT, GFont::YALIGN_CENTER); + if(trialConfig->hud.bannerShowProgress) hudFont->draw2D(rd, prog_string, hudCenter + Vector2(0, -1), scale.x * trialConfig->hud.bannerLargeFontSize, Color3::white(), Color4::clear(), GFont::XALIGN_CENTER, GFont::YALIGN_CENTER); + if(trialConfig->hud.bannerShowScore) hudFont->draw2D(rd, score_string, hudCenter + Vector2(125, 0) * scale, scale.x * trialConfig->hud.bannerSmallFontSize, Color3::white(), Color4::clear(), GFont::XALIGN_RIGHT, GFont::YALIGN_CENTER); } // Draw any static HUD elements - for (StaticHudElement element : sessConfig->hud.staticElements) { + for (StaticHudElement element : trialConfig->hud.staticElements) { if (!hudTextures.containsKey(element.filename)) continue; // Skip any items we haven't loaded const shared_ptr texture = hudTextures[element.filename]; // Get the loaded texture for this element const Vector2 size = element.scale * scale * texture->vector2Bounds(); // Get the final size of the image From 2d6bc4ec353f3d9c2991c65151e84d1c3d05dd67 Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Wed, 10 Aug 2022 11:07:41 -0400 Subject: [PATCH 10/38] Minor fix for respawnTargets() in tests --- tests/FPSciTests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/FPSciTests.cpp b/tests/FPSciTests.cpp index a39ed096..12ab2db2 100644 --- a/tests/FPSciTests.cpp +++ b/tests/FPSciTests.cpp @@ -171,7 +171,7 @@ void FPSciTests::zeroCameraRotation() int FPSciTests::respawnTargets() { s_app->sess->clearTargets(); - s_app->sess->initTargetAnimation(); + s_app->sess->initTargetAnimation(true); // TODO: investigate why it takes a frame for targets to be placed. Without this, the transform below is clobbered. s_app->oneFrame(); From 838888ea593c6d1e0144633a453a82f80b9582c3 Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Mon, 15 Aug 2022 11:50:28 -0400 Subject: [PATCH 11/38] Add session::presentQuestions() --- source/FPSciApp.cpp | 2 +- source/FPSciApp.h | 2 +- source/Session.cpp | 58 +++++++++++++++++++++++++-------------------- source/Session.h | 1 + 4 files changed, 35 insertions(+), 28 deletions(-) diff --git a/source/FPSciApp.cpp b/source/FPSciApp.cpp index 13a8f00c..135c133a 100644 --- a/source/FPSciApp.cpp +++ b/source/FPSciApp.cpp @@ -341,7 +341,7 @@ Color4 FPSciApp::lerpColor(Array colors, float a) { } } -void FPSciApp::updateControls(bool firstSession) { +void FPSciApp::updateControls(const bool firstSession) { // Update the user settings window updateUserMenu = true; if(!firstSession) m_showUserMenu = sessConfig->menu.showMenuBetweenSessions; diff --git a/source/FPSciApp.h b/source/FPSciApp.h index 50421b2d..18914f7e 100644 --- a/source/FPSciApp.h +++ b/source/FPSciApp.h @@ -130,7 +130,7 @@ class FPSciApp : public GApp { /** Called from onInit */ void makeGUI(); - void updateControls(bool firstSession = false); + void updateControls(const bool firstSession = false); void loadConfigs(const ConfigFiles& configs); diff --git a/source/Session.cpp b/source/Session.cpp index ee46c8c9..5ef80290 100644 --- a/source/Session.cpp +++ b/source/Session.cpp @@ -461,32 +461,7 @@ void Session::updatePresentationState() m_currBlock++; // Increment the block index if (m_currBlock > m_sessConfig->blockCount) { // Check for end of session (all blocks complete) - if (m_sessConfig->questionArray.size() > 0 && m_currQuestionIdx < m_sessConfig->questionArray.size()) { - // Pop up question dialog(s) here if we need to - if (m_currQuestionIdx == -1) { - m_currQuestionIdx = 0; - m_app->presentQuestion(m_sessConfig->questionArray[m_currQuestionIdx]); - } - else if (!m_app->dialog->visible()) { // Check for whether dialog is closed (otherwise we are waiting for input) - if (m_app->dialog->complete) { // Has this dialog box been completed? (or was it closed without an answer?) - m_sessConfig->questionArray[m_currQuestionIdx].result = m_app->dialog->result; // Store response w/ quesiton - if (m_sessConfig->logger.enable) { - logger->addQuestion(m_sessConfig->questionArray[m_currQuestionIdx], m_sessConfig->id, m_app->dialog); // Log the question and its answer - } - m_currQuestionIdx++; - if (m_currQuestionIdx < m_sessConfig->questionArray.size()) { // Double check we have a next question before launching the next question - m_app->presentQuestion(m_sessConfig->questionArray[m_currQuestionIdx]); // Present the next question (if there is one) - } - else { - m_app->dialog.reset(); // Null the dialog pointer when all questions complete - } - } - else { - m_app->presentQuestion(m_sessConfig->questionArray[m_currQuestionIdx]); // Relaunch the same dialog (this wasn't completed) - } - } - } - else { + if(presentQuestions(m_sessConfig->questionArray)) { // Write final session timestamp to log if (notNull(logger) && m_sessConfig->logger.enable) { int totalTrials = 0; @@ -604,6 +579,37 @@ void Session::onSimulation(RealTime rdt, SimTime sdt, SimTime idt) accumulateFrameInfo(rdt, sdt, idt); } +bool Session::presentQuestions(Array& questions) { + if (questions.size() > 0 && m_currQuestionIdx < questions.size()) { + // Initialize if needed + if (m_currQuestionIdx == -1) { + m_currQuestionIdx = 0; + m_app->presentQuestion(m_sessConfig->questionArray[m_currQuestionIdx]); + } + // Manage answered quesions + else if (!m_app->dialog->visible()) { // Check for whether dialog is closed (otherwise we are waiting for input) + if (m_app->dialog->complete) { // Has this dialog box been completed? (or was it closed without an answer?) + questions[m_currQuestionIdx].result = m_app->dialog->result; // Store response w/ quesiton + if (m_sessConfig->logger.enable) { + logger->addQuestion(questions[m_currQuestionIdx], m_sessConfig->id, m_app->dialog); // Log the question and its answer + } + m_currQuestionIdx++; + if (m_currQuestionIdx < questions.size()) { // Double check we have a next question before launching the next question + m_app->presentQuestion(questions[m_currQuestionIdx]); // Present the next question (if there is one) + } + else { + m_app->dialog.reset(); // Null the dialog pointer when all questions complete + } + } + else { + m_app->presentQuestion(questions[m_currQuestionIdx]); // Relaunch the same dialog (this wasn't completed) + } + } + return false; + } + else return true; +} + void Session::recordTrialResponse(int destroyedTargets, int totalTargets) { if (!m_sessConfig->logger.enable) return; // Skip this if the logger is disabled diff --git a/source/Session.h b/source/Session.h index 6163f693..db82616d 100644 --- a/source/Session.h +++ b/source/Session.h @@ -264,6 +264,7 @@ class Session : public ReferenceCountedObject { String formatFeedback(const String& input); String formatCommand(const String& input); + bool presentQuestions(Array& questions); /** Insert a target into the target array/scene */ inline void insertTarget(shared_ptr target); From 6a89a8ad49632b459617424e2ba0657b9ce8b328 Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Mon, 15 Aug 2022 19:17:33 -0400 Subject: [PATCH 12/38] Minor change to updateControls() logic --- source/FPSciApp.cpp | 13 ++++++------- source/FPSciApp.h | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/source/FPSciApp.cpp b/source/FPSciApp.cpp index 135c133a..ad711676 100644 --- a/source/FPSciApp.cpp +++ b/source/FPSciApp.cpp @@ -341,11 +341,7 @@ Color4 FPSciApp::lerpColor(Array colors, float a) { } } -void FPSciApp::updateControls(const bool firstSession) { - // Update the user settings window - updateUserMenu = true; - if(!firstSession) m_showUserMenu = sessConfig->menu.showMenuBetweenSessions; - +void FPSciApp::updateControls() { // Update the waypoint manager if (notNull(waypointManager)) { waypointManager->updateControls(); } @@ -427,8 +423,9 @@ void FPSciApp::makeGUI() { m_debugMenuHeight = startupConfig.developerMode ? debugWindow->rect().height() : 0.0f; // Add the control panes here - updateControls(); + updateUserMenu = true; m_showUserMenu = experimentConfig.menu.showMenuOnStartup; + updateControls(); } void FPSciApp::exportScene() { @@ -651,6 +648,8 @@ void FPSciApp::updateSession(const String& id, const bool forceSceneReload) { if (notNull(scene())) sess->clearTargets(); // Update the application w/ the session parameters + updateUserMenu = true; + if (!m_firstSession) m_showUserMenu = sessConfig->menu.showMenuBetweenSessions; updateConfigParameters(sessConfig, forceSceneReload); // Handle results files @@ -715,7 +714,7 @@ void FPSciApp::updateConfigParameters(const shared_ptr config, const setReticle(reticleConfig.index); // Update the controls for this session - updateControls(m_firstSession); // If first session consider showing the menu + updateControls(); // If first session consider showing the menu // Update the frame rate/delay updateFrameParameters(config->render.frameDelay, config->render.frameRate); diff --git a/source/FPSciApp.h b/source/FPSciApp.h index 18914f7e..d646508e 100644 --- a/source/FPSciApp.h +++ b/source/FPSciApp.h @@ -130,7 +130,7 @@ class FPSciApp : public GApp { /** Called from onInit */ void makeGUI(); - void updateControls(const bool firstSession = false); + void updateControls(); void loadConfigs(const ConfigFiles& configs); From 9c9877bf6e60e924c29bb8bbedc9a62ee4265424 Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Mon, 15 Aug 2022 19:17:52 -0400 Subject: [PATCH 13/38] Split trial and session-level questions --- source/Session.cpp | 123 ++++++++++++++++++++++++--------------------- source/Session.h | 1 + 2 files changed, 66 insertions(+), 58 deletions(-) diff --git a/source/Session.cpp b/source/Session.cpp index 5ef80290..f72b9623 100644 --- a/source/Session.cpp +++ b/source/Session.cpp @@ -457,69 +457,74 @@ void Session::updatePresentationState() { if (stateElapsedTime > m_trialConfig->timing.trialFeedbackDuration) { - if (blockComplete()) { - m_currBlock++; // Increment the block index - if (m_currBlock > m_sessConfig->blockCount) { - // Check for end of session (all blocks complete) - if(presentQuestions(m_sessConfig->questionArray)) { - // Write final session timestamp to log - if (notNull(logger) && m_sessConfig->logger.enable) { - int totalTrials = 0; - for (int tCount : m_completedTrials) { totalTrials += tCount; } - logger->updateSessionEntry((m_remainingTrials[m_currTrialIdx] == 0), totalTrials); // Update session entry in database - } - if (m_sessConfig->logger.enable) { - endLogging(); - } - m_app->markSessComplete(m_sessConfig->id); // Add this session to user's completed sessions - - m_feedbackMessage = formatFeedback(m_sessConfig->feedback.sessComplete); // Update the feedback message - m_currQuestionIdx = -1; - newState = PresentationState::sessionFeedback; + bool allAnswered = presentQuestions(m_trialConfig->questionArray); // Present any trial-level questions + if (allAnswered) { + m_currQuestionIdx = -1; // Reset the question index + if (blockComplete()) { + m_currBlock++; + if (m_currBlock > m_sessConfig->blockCount) { // End of session (all blocks complete) + newState = PresentationState::sessionFeedback; + } + else { // Block is complete but session isn't + m_feedbackMessage = formatFeedback(m_sessConfig->feedback.blockComplete); + updateBlock(); + newState = PresentationState::initial; } } - else { // Block is complete but session isn't - m_feedbackMessage = formatFeedback(m_sessConfig->feedback.blockComplete); - updateBlock(); - newState = PresentationState::initial; + else { // Individual trial complete, go back to reference target + m_feedbackMessage = ""; // Clear the feedback message + nextCondition(); + newState = PresentationState::referenceTarget; } } - else { - m_feedbackMessage = ""; // Clear the feedback message - nextCondition(); - newState = PresentationState::referenceTarget; - } } } else if (currentState == PresentationState::sessionFeedback) { if (m_hasSession) { if (stateElapsedTime > m_sessConfig->timing.sessionFeedbackDuration && (!m_sessConfig->timing.sessionFeedbackRequireClick || !m_app->shootButtonUp)) { - newState = PresentationState::complete; - - // Save current user config and status - m_app->saveUserConfig(true); - - closeSessionProcesses(); // Close the process we started at session start (if there is one) - runSessionCommands("end"); // Launch processes for the end of the session - - Array remaining = m_app->updateSessionDropDown(); - if (remaining.size() == 0) { - m_feedbackMessage = formatFeedback(m_sessConfig->feedback.allSessComplete); // Update the feedback message - moveOn = false; - if (m_app->experimentConfig.closeOnComplete || m_sessConfig->closeOnComplete) { - m_app->quitRequest(); + bool allAnswered = presentQuestions(m_sessConfig->questionArray); // Ask session-level questions + if (allAnswered) { // Present questions until done here + // Write final session timestamp to log + if (notNull(logger) && m_sessConfig->logger.enable) { + int totalTrials = 0; + for (int tCount : m_completedTrials) { totalTrials += tCount; } + logger->updateSessionEntry((m_remainingTrials[m_currTrialIdx] == 0), totalTrials); // Update session entry in database } - } - else { - m_feedbackMessage = formatFeedback(m_sessConfig->feedback.sessComplete); // Update the feedback message - if (m_sessConfig->closeOnComplete) { - m_app->quitRequest(); + if (m_sessConfig->logger.enable) { + endLogging(); } - moveOn = true; // Check for session complete (signal start of next session) - } + m_app->markSessComplete(m_sessConfig->id); // Add this session to user's completed sessions + + m_feedbackMessage = formatFeedback(m_sessConfig->feedback.sessComplete); // Update the feedback message + m_currQuestionIdx = -1; - if (m_app->experimentConfig.closeOnComplete) { - m_app->quitRequest(); + newState = PresentationState::complete; + + // Save current user config and status + m_app->saveUserConfig(true); + + closeSessionProcesses(); // Close the process we started at session start (if there is one) + runSessionCommands("end"); // Launch processes for the end of the session + + Array remaining = m_app->updateSessionDropDown(); + if (remaining.size() == 0) { + m_feedbackMessage = formatFeedback(m_sessConfig->feedback.allSessComplete); // Update the feedback message + moveOn = false; + if (m_app->experimentConfig.closeOnComplete || m_sessConfig->closeOnComplete) { + m_app->quitRequest(); + } + } + else { + m_feedbackMessage = formatFeedback(m_sessConfig->feedback.sessComplete); // Update the feedback message + if (m_sessConfig->closeOnComplete) { + m_app->quitRequest(); + } + moveOn = true; // Check for session complete (signal start of next session) + } + + if (m_app->experimentConfig.closeOnComplete) { + m_app->quitRequest(); + } } } } @@ -584,24 +589,26 @@ bool Session::presentQuestions(Array& questions) { // Initialize if needed if (m_currQuestionIdx == -1) { m_currQuestionIdx = 0; - m_app->presentQuestion(m_sessConfig->questionArray[m_currQuestionIdx]); + m_app->presentQuestion(questions[m_currQuestionIdx]); } // Manage answered quesions else if (!m_app->dialog->visible()) { // Check for whether dialog is closed (otherwise we are waiting for input) if (m_app->dialog->complete) { // Has this dialog box been completed? (or was it closed without an answer?) - questions[m_currQuestionIdx].result = m_app->dialog->result; // Store response w/ quesiton - if (m_sessConfig->logger.enable) { - logger->addQuestion(questions[m_currQuestionIdx], m_sessConfig->id, m_app->dialog); // Log the question and its answer + questions[m_currQuestionIdx].result = m_app->dialog->result; // Store response w/ question + if (m_sessConfig->logger.enable) { // Log the question and its answer + logger->addQuestion(questions[m_currQuestionIdx], m_sessConfig->id, m_app->dialog); } - m_currQuestionIdx++; + m_currQuestionIdx++; // Move to the next question if (m_currQuestionIdx < questions.size()) { // Double check we have a next question before launching the next question m_app->presentQuestion(questions[m_currQuestionIdx]); // Present the next question (if there is one) } - else { + else { // All questions complete m_app->dialog.reset(); // Null the dialog pointer when all questions complete + m_app->setMouseInputMode(FPSciApp::MouseInputMode::MOUSE_FPM); // Go back to first-person mouse + return true; } } - else { + else { // Dialog closed w/o a response (re-present the question) m_app->presentQuestion(questions[m_currQuestionIdx]); // Relaunch the same dialog (this wasn't completed) } } diff --git a/source/Session.h b/source/Session.h index db82616d..47c03dfd 100644 --- a/source/Session.h +++ b/source/Session.h @@ -135,6 +135,7 @@ class TrialConfig : public FpsConfig { // Trick from: https://www.cs.technion.ac.il/users/yechiel/c++-faq/static-init-order-on-first-use.html static FpsConfig& defaultConfig() { static FpsConfig def; + def.questionArray = Array(); // Clear the questions array (don't inherit) return def; } From 5accf8d26a6e20f11eca22b0623d3d806ab8adb7 Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Tue, 16 Aug 2022 12:01:42 -0400 Subject: [PATCH 14/38] Add parameter to allow ordered trials --- docs/experimentConfigReadme.md | 1 + source/Session.cpp | 16 ++++++++++++---- source/Session.h | 1 + 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/experimentConfigReadme.md b/docs/experimentConfigReadme.md index 60925dd2..5b417417 100644 --- a/docs/experimentConfigReadme.md +++ b/docs/experimentConfigReadme.md @@ -33,6 +33,7 @@ In addition to these general parameters each session also has a few unique param * `session id` is a short name for the session * `description` is used to indicate an additional mode for affiliated sessions (such as `real` vs `training`) * `closeOnComplete` signals to close the application whenever this session (in particular) is completed + * `randomizeTrialOrder` determines whether trials are presented in the order they are listed or in a randomized order * `blockCount` is an integer number of (repeated) groups of trials within a session, with the block number printed to the screen between "blocks" (or a single "default" block if not provided). * `trials` is a list of trials referencing the `trials` table above: * `ids` is a list of short names for the trial(s) to affiliate with the `targets` or `reactions` table below, if multiple ids are provided multiple target are spawned simultaneously in each trial diff --git a/source/Session.cpp b/source/Session.cpp index f72b9623..78e4d25a 100644 --- a/source/Session.cpp +++ b/source/Session.cpp @@ -72,6 +72,7 @@ SessionConfig::SessionConfig(const Any& any) : FpsConfig(any, defaultConfig()) { reader.get("id", id, "An \"id\" field must be provided for each session!"); reader.getIfPresent("description", description); reader.getIfPresent("closeOnComplete", closeOnComplete); + reader.getIfPresent("randomizeTrialOrder", randomizeTrialOrder); reader.getIfPresent("blockCount", blockCount); reader.get("trials", trials, format("Issues in the (required) \"trials\" array for session: \"%s\"", id)); break; @@ -171,8 +172,15 @@ bool Session::nextCondition() { } } if (unrunTrialIdxs.size() == 0) return false; - int idx = Random::common().integer(0, unrunTrialIdxs.size()-1); - m_currTrialIdx = unrunTrialIdxs[idx]; + + if (m_sessConfig->randomizeTrialOrder) { + // Pick a random trial from within the array + int idx = Random::common().integer(0, unrunTrialIdxs.size() - 1); + m_currTrialIdx = unrunTrialIdxs[idx]; + } + else { // Pick the first remaining trial + m_currTrialIdx = unrunTrialIdxs[0]; + } // Get and update the trial configuration m_trialConfig = TrialConfig::createShared(m_sessConfig->trials[m_currTrialIdx]); @@ -457,9 +465,9 @@ void Session::updatePresentationState() { if (stateElapsedTime > m_trialConfig->timing.trialFeedbackDuration) { - bool allAnswered = presentQuestions(m_trialConfig->questionArray); // Present any trial-level questions + bool allAnswered = presentQuestions(m_trialConfig->questionArray); // Present any trial-level questions if (allAnswered) { - m_currQuestionIdx = -1; // Reset the question index + m_currQuestionIdx = -1; // Reset the question index if (blockComplete()) { m_currBlock++; if (m_currBlock > m_sessConfig->blockCount) { // End of session (all blocks complete) diff --git a/source/Session.h b/source/Session.h index 47c03dfd..21462836 100644 --- a/source/Session.h +++ b/source/Session.h @@ -150,6 +150,7 @@ class SessionConfig : public FpsConfig { int blockCount = 1; ///< Default to just 1 block per session Array trials; ///< Array of trials (and their counts) to be performed bool closeOnComplete = false; ///< Close application on session completed? + bool randomizeTrialOrder = true; ///< Randomize order of trials presented within the session SessionConfig() : FpsConfig(defaultConfig()) {} SessionConfig(const Any& any); From 4fcb66e84d219fd3016907619c40fb37d020a37f Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Tue, 16 Aug 2022 12:29:45 -0400 Subject: [PATCH 15/38] Add trial-level question logging --- source/Logger.cpp | 9 ++++++++- source/Logger.h | 2 +- source/Session.cpp | 8 +++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/source/Logger.cpp b/source/Logger.cpp index 5263d0a0..afcc0f45 100644 --- a/source/Logger.cpp +++ b/source/Logger.cpp @@ -337,6 +337,8 @@ void FPSciLogger::createQuestionsTable() { Columns questionColumns = { {"time", "text"}, {"session_id", "text"}, + {"trial_id", "integer" }, + {"trial_index", "integer"}, {"question", "text"}, {"response_array", "text"}, {"key_array", "text"}, @@ -346,7 +348,7 @@ void FPSciLogger::createQuestionsTable() { createTableInDB(m_db, "Questions", questionColumns); } -void FPSciLogger::addQuestion(Question q, String session, const shared_ptr& dialog) { +void FPSciLogger::addQuestion(const Question& q, const String& session, const shared_ptr& dialog, const int trial_id, const int trial_idx) { const String time = genUniqueTimestamp(); const String optStr = Any(q.options).unparse(); const String keyStr = Any(q.optionKeys).unparse(); @@ -354,9 +356,14 @@ void FPSciLogger::addQuestion(Question q, String session, const shared_ptr(dialog)->options()).unparse(); } + String trialIdStr = trial_id < 0 ? "NULL" : "'" + String(std::to_string(trial_id)) + "'"; + String trialIdxStr = trial_idx < 0 ? "NULL" : "'" + String(std::to_string(trial_idx)) + "'"; + RowEntry rowContents = { "'" + time + "'", "'" + session + "'", + trialIdStr, + trialIdxStr, "'" + q.prompt + "'", "'" + optStr + "'", "'" + keyStr + "'", diff --git a/source/Logger.h b/source/Logger.h index 964a227d..3c19e9d7 100644 --- a/source/Logger.h +++ b/source/Logger.h @@ -156,7 +156,7 @@ class FPSciLogger : public ReferenceCountedObject { static String genFileTimestamp(); /** Record a question and its response */ - void addQuestion(Question question, String session, const shared_ptr& dialog); + void addQuestion(const Question& question, const String& session, const shared_ptr& dialog, const int trial_id=-1, const int trial_idx=-1); /** Add a target to an experiment */ void addTarget(const String& name, const shared_ptr& targetConfig, const String& spawnTime, const float& size, const Point2& spawnEcc); diff --git a/source/Session.cpp b/source/Session.cpp index 78e4d25a..279a72a7 100644 --- a/source/Session.cpp +++ b/source/Session.cpp @@ -604,7 +604,13 @@ bool Session::presentQuestions(Array& questions) { if (m_app->dialog->complete) { // Has this dialog box been completed? (or was it closed without an answer?) questions[m_currQuestionIdx].result = m_app->dialog->result; // Store response w/ question if (m_sessConfig->logger.enable) { // Log the question and its answer - logger->addQuestion(questions[m_currQuestionIdx], m_sessConfig->id, m_app->dialog); + if (currentState == PresentationState::trialFeedback) { + // End of trial question, log trial id and index + logger->addQuestion(questions[m_currQuestionIdx], m_sessConfig->id, m_app->dialog, m_currTrialIdx, m_completedTrials[m_currTrialIdx]-1); + } + else { // End of session question, don't need to log a trial id/index + logger->addQuestion(questions[m_currQuestionIdx], m_sessConfig->id, m_app->dialog); + } } m_currQuestionIdx++; // Move to the next question if (m_currQuestionIdx < questions.size()) { // Double check we have a next question before launching the next question From 2402d59a733d4a5b6a1061065f9a7016539835c7 Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Tue, 16 Aug 2022 12:33:25 -0400 Subject: [PATCH 16/38] Add randomizeTrialOrder to toAny --- source/Session.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/source/Session.cpp b/source/Session.cpp index 279a72a7..807c730a 100644 --- a/source/Session.cpp +++ b/source/Session.cpp @@ -91,6 +91,7 @@ Any SessionConfig::toAny(const bool forceAll) const { a["id"] = id; a["description"] = description; if (forceAll || def.closeOnComplete != closeOnComplete) a["closeOnComplete"] = closeOnComplete; + if (forceAll || def.randomizeTrialOrder != randomizeTrialOrder) a["randomizeTrialOrder"] = randomizeTrialOrder; if (forceAll || def.blockCount != blockCount) a["blockCount"] = blockCount; a["trials"] = trials; return a; From edbc4e9dba4e1cdb0000303f031dacccec8fb6cb Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Tue, 16 Aug 2022 16:00:20 -0400 Subject: [PATCH 17/38] Add trialParametersToLog parameter --- docs/general_config.md | 18 ++++++++++-------- source/FpsConfig.cpp | 2 ++ source/FpsConfig.h | 1 + source/Logger.cpp | 27 +++++++++++++++++++++------ source/Logger.h | 9 ++++++--- source/Session.cpp | 1 + 6 files changed, 41 insertions(+), 17 deletions(-) diff --git a/docs/general_config.md b/docs/general_config.md index a7b282cf..62c105a4 100644 --- a/docs/general_config.md +++ b/docs/general_config.md @@ -265,14 +265,15 @@ Note: The sound played when `referenceTargetPlayFireSound` is set to `true` is t ## Logging Controls As part of the general configuration parameters several controls over reporting of data via the output SQL database are provided. These flags and their functions are described below. -| Parameter Name | Units | Description | -|-----------------------|-------|----------------------------------------------------------------------------------| -|`logEnable` |`bool` | Enables the logger and creation of an output database | -|`logTargetTrajectories`|`bool` | Whether or not to log target position to the `Target_Trajectory` table | -|`logFrameInfo` |`bool` | Whether or not to log frame info into the `Frame_Info` table | -|`logPlayerActions` |`bool` | Whether or not to log player actions into the `Player_Action` table | -|`logTrialResponse` |`bool` | Whether or not to log trial responses into the `Trials` table | -|`sessionParametersToLog` |`Array`| A list of additional parameter names (from the config) to log | +| Parameter Name | Units | Description | +|---------------------------|-------|----------------------------------------------------------------------------------| +|`logEnable` |`bool` | Enables the logger and creation of an output database | +|`logTargetTrajectories` |`bool` | Whether or not to log target position to the `Target_Trajectory` table | +|`logFrameInfo` |`bool` | Whether or not to log frame info into the `Frame_Info` table | +|`logPlayerActions` |`bool` | Whether or not to log player actions into the `Player_Action` table | +|`logTrialResponse` |`bool` | Whether or not to log trial responses into the `Trials` table | +|`sessionParametersToLog` |`Array`| A list of additional parameter names (from the config) to log with ecah `Sessions` table entry | +|`trialParametersToLog` |`Array`| A list of additional parameter names (from the config) to log with each `Trials` table entry | ``` "logEnable" : true, @@ -281,6 +282,7 @@ As part of the general configuration parameters several controls over reporting "logPlayerActions": true, "logTrialResponse": true, "sessionParametersToLog" : ["frameRate", "frameDelay"], +"trialParametersToLog": [], ``` ### Logging Session Parameters diff --git a/source/FpsConfig.cpp b/source/FpsConfig.cpp index 065a56fb..4298af98 100644 --- a/source/FpsConfig.cpp +++ b/source/FpsConfig.cpp @@ -534,6 +534,7 @@ void LoggerConfig::load(FPSciAnyTableReader reader, int settingsVersion) { reader.getIfPresent("logUsers", logUsers); reader.getIfPresent("logOnChange", logOnChange); reader.getIfPresent("sessionParametersToLog", sessParamsToLog); + reader.getIfPresent("trialParametersToLog", trialParamsToLog); reader.getIfPresent("logToSingleDb", logToSingleDb); break; default: @@ -552,6 +553,7 @@ Any LoggerConfig::addToAny(Any a, bool forceAll) const { if (forceAll || def.logUsers != logUsers) a["logUsers"] = logUsers; if (forceAll || def.logOnChange != logOnChange) a["logOnChange"] = logOnChange; if (forceAll || def.sessParamsToLog != sessParamsToLog) a["sessionParametersToLog"] = sessParamsToLog; + if (forceAll || def.trialParamsToLog != trialParamsToLog) a["trialParametersToLog"] = trialParamsToLog; if (forceAll || def.logToSingleDb != logToSingleDb) a["logToSingleDb"] = logToSingleDb; return a; } diff --git a/source/FpsConfig.h b/source/FpsConfig.h index 85777e5d..ca7ef008 100644 --- a/source/FpsConfig.h +++ b/source/FpsConfig.h @@ -280,6 +280,7 @@ class LoggerConfig { // Session parameter logging Array sessParamsToLog = { "frameRate", "frameDelay" }; ///< Parameter names to log to the Sessions table of the DB + Array trialParamsToLog = {}; ///< Parameter names to log to the Trials table of the DB void load(FPSciAnyTableReader reader, int settingsVersion = 1); Any addToAny(Any a, bool forceAll = false) const; diff --git a/source/Logger.cpp b/source/Logger.cpp index afcc0f45..4f9c2a54 100644 --- a/source/Logger.cpp +++ b/source/Logger.cpp @@ -53,10 +53,19 @@ void FPSciLogger::initResultsFile(const String& filename, // Create tables if a new log file is opened if (createNewFile) { createExperimentsTable(expConfigFilename); - createSessionsTable(sessConfig); + createSessionsTable(sessConfig->logger.sessParamsToLog); createTargetTypeTable(); createTargetsTable(); - createTrialsTable(); + + // Build up array of all trial parameters to log + m_trialParams = sessConfig->logger.trialParamsToLog; + for (const TrialConfig& t : sessConfig->trials) { + for (const String& p : t.logger.trialParamsToLog) { + if (!m_trialParams.contains(p)) m_trialParams.append(p); + } + } + createTrialsTable(m_trialParams); + createTargetTrajectoryTable(); createPlayerActionTable(); createFrameInfoTable(); @@ -79,7 +88,7 @@ void FPSciLogger::initResultsFile(const String& filename, // Create any table to do lookup here Any a = sessConfig->toAny(true); // Add the looked up values - for (String name : sessConfig->logger.sessParamsToLog) { sessValues.append("'" + a[name].unparse() + "'"); } + for (const String& name : sessConfig->logger.sessParamsToLog) { sessValues.append("'" + a[name].unparse() + "'"); } // add header row insertRowIntoDB(m_db, "Sessions", sessValues); @@ -110,7 +119,7 @@ void FPSciLogger::createExperimentsTable(const String& expConfigFilename) { insertRowIntoDB(m_db, "Experiments", expRow); } -void FPSciLogger::createSessionsTable(const shared_ptr& sessConfig) { +void FPSciLogger::createSessionsTable(const Array& sessParams) { // Session description (time and subject ID) Columns sessColumns = { // format: column name, data type, sqlite modifier(s) @@ -123,7 +132,7 @@ void FPSciLogger::createSessionsTable(const shared_ptr& sessConfi { "trials_complete", "integer" } }; // add any user-specified parameters as headers - for (String name : sessConfig->logger.sessParamsToLog) { sessColumns.append({ "'" + name + "'", "text", "NOT NULL" }); } + for (const String& name : sessParams) { sessColumns.append({ "'" + name + "'", "text", "NOT NULL" }); } createTableInDB(m_db, "Sessions", sessColumns); // no need of Primary Key for this table. } @@ -216,7 +225,12 @@ void FPSciLogger::addTarget(const String& name, const shared_ptr& logTargetInfo(targetValues); } -void FPSciLogger::createTrialsTable() { +void FPSciLogger::addTrialParamValues(TrialValues& t, const shared_ptr& config) { + Any a = config->toAny(true); + for (const String& p : m_trialParams) { t.append("'" + a[p].unparse() + "'"); } +} + +void FPSciLogger::createTrialsTable(const Array& trialParams) { // Trials table Columns trialColumns = { { "session_id", "text" }, @@ -230,6 +244,7 @@ void FPSciLogger::createTrialsTable() { { "destroyed_targets", "integer" }, { "total_targets", "integer" } }; + for (String name : trialParams) { trialColumns.append({ "'" + name + "'", "text", "NOT NULL" }); } createTableInDB(m_db, "Trials", trialColumns); } diff --git a/source/Logger.h b/source/Logger.h index 3c19e9d7..1884cc2f 100644 --- a/source/Logger.h +++ b/source/Logger.h @@ -45,6 +45,8 @@ class FPSciLogger : public ReferenceCountedObject { std::mutex m_queueMutex; std::condition_variable m_queueCV; + Array m_trialParams; ///< Storage for trial parameters + // Output queues for reported data storage Array m_frameInfo; ///< Storage for frame info (sdt, idt, rdt) Array m_playerActions; ///< Storage for player action (hit, miss, aim) @@ -102,10 +104,10 @@ class FPSciLogger : public ReferenceCountedObject { // Functions that set up the database schema /** Create a session table with columns as specified by the provided sessionConfig */ void createExperimentsTable(const String& expConfigFilename); - void createSessionsTable(const shared_ptr& sessConfig); + void createSessionsTable(const Array& sessParams); void createTargetTypeTable(); void createTargetsTable(); - void createTrialsTable(); + void createTrialsTable(const Array& trialParams); void createTargetTrajectoryTable(); void createPlayerActionTable(); void createFrameInfoTable(); @@ -139,7 +141,8 @@ class FPSciLogger : public ReferenceCountedObject { void logTargetLocation(const TargetLocation& targetLocation) { addToQueue(m_targetLocations, targetLocation); } void logTargetInfo(const TargetInfo& targetInfo) { addToQueue(m_targets, targetInfo); } void logTrial(const TrialValues& trial) { addToQueue(m_trials, trial); } - + + void addTrialParamValues(TrialValues& trial, const shared_ptr& config); void logUserConfig(const UserConfig& userConfig, const String& sessId, const Vector2& sessTurnScale); void logTargetTypes(const Array>& targets); diff --git a/source/Session.cpp b/source/Session.cpp index 807c730a..579efd36 100644 --- a/source/Session.cpp +++ b/source/Session.cpp @@ -649,6 +649,7 @@ void Session::recordTrialResponse(int destroyedTargets, int totalTargets) String(std::to_string(destroyedTargets)), String(std::to_string(totalTargets)) }; + logger->addTrialParamValues(trialValues, m_trialConfig); logger->logTrial(trialValues); } } From f2462cf99fb9890648bd428ecc3a5089ad9b3b6c Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Wed, 17 Aug 2022 12:12:00 -0400 Subject: [PATCH 18/38] Fix developer controls for TrialConfig --- source/FPSciApp.cpp | 13 ++++++------- source/FPSciApp.h | 2 +- source/GuiElements.cpp | 4 ++-- source/GuiElements.h | 10 +++++----- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/source/FPSciApp.cpp b/source/FPSciApp.cpp index ad711676..80ec7269 100644 --- a/source/FPSciApp.cpp +++ b/source/FPSciApp.cpp @@ -341,7 +341,7 @@ Color4 FPSciApp::lerpColor(Array colors, float a) { } } -void FPSciApp::updateControls() { +void FPSciApp::updateDeveloperControls(const shared_ptr& config) { // Update the waypoint manager if (notNull(waypointManager)) { waypointManager->updateControls(); } @@ -353,7 +353,7 @@ void FPSciApp::updateControls() { rect = m_playerControls->rect(); removeWidget(m_playerControls); } - m_playerControls = PlayerControls::create(*sessConfig, std::bind(&FPSciApp::exportScene, this), theme); + m_playerControls = PlayerControls::create(*config, std::bind(&FPSciApp::exportScene, this), theme); m_playerControls->setVisible(visible); if (!rect.isEmpty()) m_playerControls->setRect(rect); addWidget(m_playerControls); @@ -366,7 +366,7 @@ void FPSciApp::updateControls() { rect = m_renderControls->rect(); removeWidget(m_renderControls); } - m_renderControls = RenderControls::create(this, *sessConfig, renderFPS, numReticles, sceneBrightness, theme, MAX_HISTORY_TIMING_FRAMES); + m_renderControls = RenderControls::create(this, *config, renderFPS, numReticles, sceneBrightness, theme, MAX_HISTORY_TIMING_FRAMES); m_renderControls->setVisible(visible); if (!rect.isEmpty()) m_renderControls->setRect(rect); addWidget(m_renderControls); @@ -379,7 +379,7 @@ void FPSciApp::updateControls() { rect = m_weaponControls->rect(); removeWidget(m_weaponControls); } - m_weaponControls = WeaponControls::create(sessConfig->weapon, theme); + m_weaponControls = WeaponControls::create(config->weapon, theme); m_weaponControls->setVisible(visible); if (!rect.isEmpty()) m_weaponControls->setRect(rect); addWidget(m_weaponControls); @@ -425,7 +425,7 @@ void FPSciApp::makeGUI() { // Add the control panes here updateUserMenu = true; m_showUserMenu = experimentConfig.menu.showMenuOnStartup; - updateControls(); + updateDeveloperControls(std::make_shared((FpsConfig)experimentConfig)); } void FPSciApp::exportScene() { @@ -713,8 +713,7 @@ void FPSciApp::updateConfigParameters(const shared_ptr config, const reticleConfig.changeTimeS = config->reticle.changeTimeSpecified ? config->reticle.changeTimeS : currentUser()->reticle.changeTimeS; setReticle(reticleConfig.index); - // Update the controls for this session - updateControls(); // If first session consider showing the menu + updateDeveloperControls(config); // Update the frame rate/delay updateFrameParameters(config->render.frameDelay, config->render.frameRate); diff --git a/source/FPSciApp.h b/source/FPSciApp.h index d646508e..c71934d8 100644 --- a/source/FPSciApp.h +++ b/source/FPSciApp.h @@ -130,7 +130,7 @@ class FPSciApp : public GApp { /** Called from onInit */ void makeGUI(); - void updateControls(); + void updateDeveloperControls(const shared_ptr& config); void loadConfigs(const ConfigFiles& configs); diff --git a/source/GuiElements.cpp b/source/GuiElements.cpp index 34d0cd87..9b58f201 100644 --- a/source/GuiElements.cpp +++ b/source/GuiElements.cpp @@ -150,7 +150,7 @@ void WaypointDisplay::setManager(WidgetManager *manager) { } } -PlayerControls::PlayerControls(SessionConfig& config, std::function exportCallback, +PlayerControls::PlayerControls(FpsConfig& config, std::function exportCallback, const shared_ptr& theme, float width, float height) : GuiWindow("Player Controls", theme, Rect2D::xywh(5, 5, width, height), GuiTheme::NORMAL_WINDOW_STYLE, GuiWindow::HIDE_ON_CLOSE) { @@ -199,7 +199,7 @@ PlayerControls::PlayerControls(SessionConfig& config, std::function expo moveTo(Vector2(440, 300)); } -RenderControls::RenderControls(FPSciApp* app, SessionConfig& config, bool& drawFps, const int numReticles, float& brightness, +RenderControls::RenderControls(FPSciApp* app, FpsConfig& config, bool& drawFps, const int numReticles, float& brightness, const shared_ptr& theme, const int maxFrameDelay, const float minFrameRate, const float maxFrameRate, float width, float height) : GuiWindow("Render Controls", theme, Rect2D::xywh(5,5,width,height), GuiTheme::NORMAL_WINDOW_STYLE, GuiWindow::HIDE_ON_CLOSE), m_app(app) { diff --git a/source/GuiElements.h b/source/GuiElements.h index fdfb6c79..2882126a 100644 --- a/source/GuiElements.h +++ b/source/GuiElements.h @@ -7,7 +7,7 @@ #include "FPSciAnyTableReader.h" class FPSciApp; -class SessionConfig; +class FpsConfig; class MenuConfig { public: @@ -107,11 +107,11 @@ class WaypointDisplay : public GuiWindow { class PlayerControls : public GuiWindow { protected: - PlayerControls(SessionConfig& config, std::function exportCallback, + PlayerControls(FpsConfig& config, std::function exportCallback, const shared_ptr& theme, float width = 400.0f, float height = 10.0f); public: - static shared_ptr create(SessionConfig& config, std::function exportCallback, + static shared_ptr create(FpsConfig& config, std::function exportCallback, const shared_ptr& theme, float width = 400.0f, float height = 10.0f) { return createShared(config, exportCallback, theme, width, height); } @@ -126,10 +126,10 @@ class RenderControls : public GuiWindow { void updateUserMenu(void); - RenderControls(FPSciApp* app, SessionConfig& config, bool& drawFps, const int numReticles, float& brightness, + RenderControls(FPSciApp* app, FpsConfig& config, bool& drawFps, const int numReticles, float& brightness, const shared_ptr& theme, const int maxFrameDelay = 360, const float minFrameRate = 1.0f, const float maxFrameRate=1000.0f, float width=400.0f, float height=10.0f); public: - static shared_ptr create(FPSciApp* app, SessionConfig& config, bool& drawFps, const int numReticles, float& brightness, + static shared_ptr create(FPSciApp* app, FpsConfig& config, bool& drawFps, const int numReticles, float& brightness, const shared_ptr& theme, const int maxFrameDelay = 360, const float minFrameRate = 1.0f, const float maxFrameRate=1000.0f, float width = 400.0f, float height = 10.0f) { return createShared(app, config, drawFps, numReticles, brightness, theme, maxFrameDelay, minFrameRate, maxFrameRate, width, height); } From eed469cb0ac58bb1c6498eac30b5a2669e9f94d9 Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Thu, 18 Aug 2022 12:17:28 -0400 Subject: [PATCH 19/38] Fix some respawn mechanic bugs --- source/FPSciApp.cpp | 12 ++++++------ source/FPSciApp.h | 4 ++-- source/Session.cpp | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/source/FPSciApp.cpp b/source/FPSciApp.cpp index 80ec7269..6695a08f 100644 --- a/source/FPSciApp.cpp +++ b/source/FPSciApp.cpp @@ -523,7 +523,7 @@ void FPSciApp::updateFrameParameters(int frameDelay, float frameRate) { setFrameDuration(dt, simStepDuration()); } -void FPSciApp::initPlayer(const shared_ptr config, bool setSpawnPosition) { +void FPSciApp::initPlayer(const shared_ptr config, const bool respawn, bool setSpawnPosition) { shared_ptr pscene = typedScene(); shared_ptr player = scene()->typedEntity("player"); // Get player from the scene @@ -612,7 +612,7 @@ void FPSciApp::initPlayer(const shared_ptr config, bool setSpawnPosit player->crouchHeight = &config->player.crouchHeight; // Respawn player - player->respawn(); + if (respawn) player->respawn(); updateMouseSensitivity(); // Set initial heading for session @@ -650,7 +650,7 @@ void FPSciApp::updateSession(const String& id, const bool forceSceneReload) { // Update the application w/ the session parameters updateUserMenu = true; if (!m_firstSession) m_showUserMenu = sessConfig->menu.showMenuBetweenSessions; - updateConfigParameters(sessConfig, forceSceneReload); + updateConfigParameters(sessConfig, forceSceneReload, true); // Handle results files const String resultsDirPath = startupConfig.experimentList[experimentIdx].resultsDirPath; @@ -705,7 +705,7 @@ void FPSciApp::updateTrial(const shared_ptr config, const bool forc updateConfigParameters(config, forceSceneReload); } -void FPSciApp::updateConfigParameters(const shared_ptr config, const bool forceSceneReload) { +void FPSciApp::updateConfigParameters(const shared_ptr config, const bool forceSceneReload, const bool respawn) { // Update reticle reticleConfig.index = config->reticle.indexSpecified ? config->reticle.index : currentUser()->reticle.index; reticleConfig.scale = config->reticle.scaleSpecified ? config->reticle.scale : currentUser()->reticle.scale; @@ -759,7 +759,7 @@ void FPSciApp::updateConfigParameters(const shared_ptr config, const } // Player parameters - initPlayer(config); + initPlayer(config, respawn); // Check for play mode specific parameters if (notNull(weapon)) weapon->clearDecals(); @@ -813,7 +813,7 @@ void FPSciApp::onAfterLoadScene(const Any& any, const String& sceneName) { m_initialCameraFrames.set(cam->name(), cam->frame()); } - initPlayer(trialConfig, true); // Initialize the player (first time for this scene) + initPlayer(trialConfig, false, true); // Initialize the player (first time for this scene) if (weapon) { weapon->setScene(scene()); diff --git a/source/FPSciApp.h b/source/FPSciApp.h index c71934d8..e9ba5d3e 100644 --- a/source/FPSciApp.h +++ b/source/FPSciApp.h @@ -138,7 +138,7 @@ class FPSciApp : public GApp { /** Initializes player settings from configs and resets player to initial position Also updates mouse sensitivity. */ - void initPlayer(const shared_ptr config, bool firstSpawn = false); + void initPlayer(const shared_ptr config, const bool respawn = false, bool firstSpawn = false) ; /** Move a window to the center of the display */ void moveToCenter(shared_ptr window) { @@ -257,7 +257,7 @@ class FPSciApp : public GApp { /** Updates experiment state to the provided session id and updates player parameters (including mouse sensitivity) */ virtual void updateSession(const String& id, bool forceSceneReload = false); void updateTrial(const shared_ptr config, const bool forceSceneReload = false); - void updateConfigParameters(const shared_ptr config, bool forceSceneReload = false); + void updateConfigParameters(const shared_ptr config, bool forceSceneReload = false, const bool respawn = false); void updateFrameParameters(int frameDelay, float frameRate); void updateTargetColor(const shared_ptr& target); void presentQuestion(Question question); diff --git a/source/Session.cpp b/source/Session.cpp index 579efd36..362f9b66 100644 --- a/source/Session.cpp +++ b/source/Session.cpp @@ -409,7 +409,7 @@ void Session::updatePresentationState() if (currentState == PresentationState::initial) { - if (m_sessConfig->player.stillBetweenTrials) { + if (m_trialConfig->player.stillBetweenTrials) { m_player->setMoveEnable(false); } if (!(m_app->shootButtonUp && m_trialConfig->timing.clickToStart)) { @@ -428,7 +428,7 @@ void Session::updatePresentationState() if (stateElapsedTime > m_pretrialDuration) { newState = PresentationState::trialTask; - if (m_sessConfig->player.stillBetweenTrials) { + if (m_trialConfig->player.stillBetweenTrials) { m_player->setMoveEnable(true); } @@ -445,10 +445,10 @@ void Session::updatePresentationState() processResponse(); clearTargets(); // clear all remaining targets newState = PresentationState::trialFeedback; - if (m_sessConfig->player.stillBetweenTrials) { + if (m_trialConfig->player.stillBetweenTrials) { m_player->setMoveEnable(false); } - if (m_sessConfig->player.resetPositionPerTrial) { + if (m_trialConfig->player.resetPositionPerTrial) { m_player->respawn(); } From 2c0ba91731109f631a734e52cc0a59d29997bd48 Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Mon, 14 Nov 2022 13:46:28 -0500 Subject: [PATCH 20/38] Add support for trial ID (logging only) --- docs/experimentConfigReadme.md | 2 ++ source/Logger.cpp | 2 +- source/Session.cpp | 7 ++++++- source/Session.h | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/experimentConfigReadme.md b/docs/experimentConfigReadme.md index 5b417417..639629de 100644 --- a/docs/experimentConfigReadme.md +++ b/docs/experimentConfigReadme.md @@ -36,6 +36,7 @@ In addition to these general parameters each session also has a few unique param * `randomizeTrialOrder` determines whether trials are presented in the order they are listed or in a randomized order * `blockCount` is an integer number of (repeated) groups of trials within a session, with the block number printed to the screen between "blocks" (or a single "default" block if not provided). * `trials` is a list of trials referencing the `trials` table above: + * `id` is an (optional) target ID that (if specified) is used for logging purposes. If unspecified the `id` defaults to the (integer) index of the trial in the `trials` array. * `ids` is a list of short names for the trial(s) to affiliate with the `targets` or `reactions` table below, if multiple ids are provided multiple target are spawned simultaneously in each trial * `count` provides the number of trials in this session (should always be an integer strictly greater than 0) @@ -66,6 +67,7 @@ An example session configuration snippet is included below: { "id" : "minimal-session", // This session inherits all settings from the experiment above it "trials" : [ + "id": "example trial"; "ids" : ["simple_target", "destination-based"], "count" : 10 ] diff --git a/source/Logger.cpp b/source/Logger.cpp index 4f9c2a54..39c72415 100644 --- a/source/Logger.cpp +++ b/source/Logger.cpp @@ -234,7 +234,7 @@ void FPSciLogger::createTrialsTable(const Array& trialParams) { // Trials table Columns trialColumns = { { "session_id", "text" }, - { "trial_id", "integer" }, + { "trial_id", "text" }, { "trial_index", "integer"}, { "block_id", "text"}, { "start_time", "text" }, diff --git a/source/Session.cpp b/source/Session.cpp index 6c1978b2..1f84b704 100644 --- a/source/Session.cpp +++ b/source/Session.cpp @@ -41,6 +41,7 @@ TrialConfig::TrialConfig(const Any& any) : FpsConfig(any, defaultConfig()) { switch (settingsVersion) { case 1: + reader.getIfPresent("id", id); reader.get("ids", ids, "An \"ids\" field must be provided for each set of trials!"); if (!reader.getIfPresent("count", count)) { count = defaultCount; @@ -643,9 +644,13 @@ void Session::recordTrialResponse(int destroyedTargets, int totalTargets) if (!m_sessConfig->logger.enable) return; // Skip this if the logger is disabled if (m_trialConfig->logger.logTrialResponse) { // Trials table. Record trial start time, end time, and task completion time. + String trialId = m_trialConfig->id; + if (trialId.empty()) { + trialId = String(std::to_string(m_currTrialIdx)); // Fall back to trial index if no trial ID is specified (empty string) + } FPSciLogger::TrialValues trialValues = { "'" + m_sessConfig->id + "'", - String(std::to_string(m_currTrialIdx)), + "'" + trialId + "'", String(std::to_string(m_completedTrials[m_currTrialIdx])), format("'Block %d'", m_currBlock), "'" + m_taskStartTime + "'", diff --git a/source/Session.h b/source/Session.h index bffc3a8a..339a0749 100644 --- a/source/Session.h +++ b/source/Session.h @@ -121,6 +121,7 @@ struct PlayerAction { /** Trial count class (optional for alternate TargetConfig/count table lookup) */ class TrialConfig : public FpsConfig { public: + String id; ///< Trial ID (used for logging) Array ids; ///< Trial ID list int count = 1; ///< Count of trials to be performed static int defaultCount; ///< Default count to use From 7c84f8411619d902e5288e913c358f41d10990ff Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Tue, 15 Nov 2022 14:36:39 -0500 Subject: [PATCH 21/38] Trial ID to Any --- source/Session.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/source/Session.cpp b/source/Session.cpp index 1f84b704..5d32149a 100644 --- a/source/Session.cpp +++ b/source/Session.cpp @@ -58,6 +58,7 @@ TrialConfig::TrialConfig(const Any& any) : FpsConfig(any, defaultConfig()) { Any TrialConfig::toAny(const bool forceAll) const { Any a = FpsConfig::toAny(forceAll); + a["id"] = id; a["ids"] = ids; a["count"] = count; return a; From df4ea22dbdb3c937c61d0f296481931b942f9644 Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Tue, 15 Nov 2022 14:43:02 -0500 Subject: [PATCH 22/38] Automatic index-based trial IDs --- source/Session.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/source/Session.cpp b/source/Session.cpp index 5d32149a..71d37e80 100644 --- a/source/Session.cpp +++ b/source/Session.cpp @@ -77,6 +77,11 @@ SessionConfig::SessionConfig(const Any& any) : FpsConfig(any, defaultConfig()) { reader.getIfPresent("randomizeTrialOrder", randomizeTrialOrder); reader.getIfPresent("blockCount", blockCount); reader.get("trials", trials, format("Issues in the (required) \"trials\" array for session: \"%s\"", id)); + for (int i = 0; i < trials.length(); i++) { + if (trials[i].id.empty()) { // Look for trials without an id + trials[i].id = String(std::to_string(i)); // Autoname w/ index + } + } break; default: debugPrintf("Settings version '%d' not recognized in SessionConfig.\n", settingsVersion); @@ -645,13 +650,9 @@ void Session::recordTrialResponse(int destroyedTargets, int totalTargets) if (!m_sessConfig->logger.enable) return; // Skip this if the logger is disabled if (m_trialConfig->logger.logTrialResponse) { // Trials table. Record trial start time, end time, and task completion time. - String trialId = m_trialConfig->id; - if (trialId.empty()) { - trialId = String(std::to_string(m_currTrialIdx)); // Fall back to trial index if no trial ID is specified (empty string) - } FPSciLogger::TrialValues trialValues = { "'" + m_sessConfig->id + "'", - "'" + trialId + "'", + "'" + m_trialConfig->id + "'", String(std::to_string(m_completedTrials[m_currTrialIdx])), format("'Block %d'", m_currBlock), "'" + m_taskStartTime + "'", From d6ce60941786b04915aa9819b3cdc6acab5fb88a Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Tue, 15 Nov 2022 15:37:17 -0500 Subject: [PATCH 23/38] Add trial ID unique check --- source/Session.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/source/Session.cpp b/source/Session.cpp index 71d37e80..eae70b44 100644 --- a/source/Session.cpp +++ b/source/Session.cpp @@ -67,6 +67,8 @@ Any TrialConfig::toAny(const bool forceAll) const { SessionConfig::SessionConfig(const Any& any) : FpsConfig(any, defaultConfig()) { TrialConfig::defaultCount = timing.defaultTrialCount; FPSciAnyTableReader reader(any); + Set uniqueIds; + switch (settingsVersion) { case 1: TrialConfig::defaultConfig() = (FpsConfig)(*this); // Setup the default configuration for trials here @@ -81,6 +83,13 @@ SessionConfig::SessionConfig(const Any& any) : FpsConfig(any, defaultConfig()) { if (trials[i].id.empty()) { // Look for trials without an id trials[i].id = String(std::to_string(i)); // Autoname w/ index } + uniqueIds.insert(trials[i].id); + if (uniqueIds.size() != i + 1) { + logPrintf("ERROR: Duplicate trial ID \"%s\" found (trials without IDs are assigned an ID equal to their index in the trials array)!\n", trials[i].id); + } + } + if (uniqueIds.size() != trials.size()) { + throw "Duplicate trial IDs found in experiment config. Check log.txt for details!"; } break; default: From 06c6824c90bba2d2d613211e91a5f71c7baf1834 Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Tue, 29 Nov 2022 12:43:40 -0500 Subject: [PATCH 24/38] toAny forceAll to false for TrialConfig --- source/Session.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Session.h b/source/Session.h index 339a0749..522b1ce2 100644 --- a/source/Session.h +++ b/source/Session.h @@ -140,7 +140,7 @@ class TrialConfig : public FpsConfig { return def; } - Any toAny(const bool forceAll = true) const; + Any toAny(const bool forceAll = false) const; }; /** Configuration for a session worth of trials */ From 0804f1ac8d7e63537e2a2aef5d1f855d0c200c4f Mon Sep 17 00:00:00 2001 From: Josef Spjut Date: Wed, 30 Nov 2022 12:23:42 -0500 Subject: [PATCH 25/38] Fix minor typo --- docs/general_config.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/general_config.md b/docs/general_config.md index 7d10b8ca..1c71e481 100644 --- a/docs/general_config.md +++ b/docs/general_config.md @@ -326,7 +326,7 @@ These flags control whether various information is written to the output files. "logToSingleDb" = true, // Log all sessions affiliated with a given experiment to the same database file "sessionParametersToLog" = ["frameRate", "frameDelay"], // Log the frame rate and frame delay to the Sessions table "trialParametersToLog": [], // Don't log any trial-level parameters by default -"logSessionDropDownUpdate" : false, // Done log changes in the session drop down +"logSessionDropDownUpdate" : false, // Don't log changes in the session drop down ``` *Note:* When `logToSingleDb` is `true` the filename used for logging is `"[experiment description]_[current user]_[experiment config hash].db"`. This hash is printed to the `log.txt` from the run in case it is needed to disambiguate results files. In addition when `logToSingleDb` is true, the `sessionParametersToLog` should match for all logged sessions to avoid potential logging issues. The experiment config hash takes into account only "valid" settings and ignores formatting only changes in the configuration file. Default values are used for the hash for anything that is not specified, so if a default is specified, the hash will match the config where the default was not specified. From 59731e79e589e3a40679dcc09177851384bec94a Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Wed, 30 Nov 2022 13:33:47 -0500 Subject: [PATCH 26/38] Rename trial "ids" to "targetIds" and document --- docs/experimentConfigReadme.md | 22 ++++++++++++---------- source/ExperimentConfig.cpp | 18 +++++++++--------- source/FpsConfig.cpp | 2 +- source/Session.cpp | 19 ++++++++++++------- source/Session.h | 4 ++-- 5 files changed, 36 insertions(+), 29 deletions(-) diff --git a/docs/experimentConfigReadme.md b/docs/experimentConfigReadme.md index 639629de..3da0d6f9 100644 --- a/docs/experimentConfigReadme.md +++ b/docs/experimentConfigReadme.md @@ -36,8 +36,8 @@ In addition to these general parameters each session also has a few unique param * `randomizeTrialOrder` determines whether trials are presented in the order they are listed or in a randomized order * `blockCount` is an integer number of (repeated) groups of trials within a session, with the block number printed to the screen between "blocks" (or a single "default" block if not provided). * `trials` is a list of trials referencing the `trials` table above: - * `id` is an (optional) target ID that (if specified) is used for logging purposes. If unspecified the `id` defaults to the (integer) index of the trial in the `trials` array. - * `ids` is a list of short names for the trial(s) to affiliate with the `targets` or `reactions` table below, if multiple ids are provided multiple target are spawned simultaneously in each trial + * `id` is an (optional) trial ID that (if specified) is used for logging purposes. If unspecified the `id` defaults to the (integer) index of the trial in the `trials` array. + * `targetIds` is a list of target names (`id` fields from within `targets` array elements) to spawn in this trial, if multiple target ids are provided multiple targets are spawned simultaneously in each trial * `count` provides the number of trials in this session (should always be an integer strictly greater than 0) #### Session Configuration Example @@ -54,23 +54,25 @@ An example session configuration snippet is included below: "trials" : [ { // Single target example - "ids" : ["simple_target"], + "targetIds" : ["simple_target"], "count": 20, }, { // Multi-target example - "ids" : ["simple_target", "world-space paramtetric", "example_target", "example_target"], - "count" : 5 + "targetIds" : ["simple_target", "world-space paramtetric", "example_target", "example_target"], + "count" : 5, } - ] + ], }, { "id" : "minimal-session", // This session inherits all settings from the experiment above it "trials" : [ - "id": "example trial"; - "ids" : ["simple_target", "destination-based"], - "count" : 10 - ] + { + "id": "example trial", + "targetIds" : ["simple_target", "destination-based"], + "count" : 10, + } + ], }, ], ``` diff --git a/source/ExperimentConfig.cpp b/source/ExperimentConfig.cpp index f8845259..d08900ef 100644 --- a/source/ExperimentConfig.cpp +++ b/source/ExperimentConfig.cpp @@ -58,9 +58,9 @@ void ExperimentConfig::init() { } else { // Targets are present (make sure no 2 have the same ID) - Array ids; + Array targetIds; for (TargetConfig target : targets) { - if (!ids.contains(target.id)) { ids.append(target.id); } + if (!targetIds.contains(target.id)) { targetIds.append(target.id); } else { // This is a repeat entry, throw an exception throw format("Found duplicate target configuration for target: \"%s\"", target.id); @@ -144,7 +144,7 @@ Array>> ExperimentConfig::getTargetsByTrial(int s // Iterate through the trials for (int i = 0; i < sessions[sessionIndex].trials.size(); i++) { Array> targets; - for (String id : sessions[sessionIndex].trials[i].ids) { + for (String id : sessions[sessionIndex].trials[i].targetIds) { const shared_ptr t = getTargetConfigById(id); targets.append(t); } @@ -158,7 +158,7 @@ Array> ExperimentConfig::getSessionTargets(const String Array> targets; Array loggedIds; for (auto trial : sessions[idx].trials) { - for (String& id : trial.ids) { + for (String& id : trial.targetIds) { if (!loggedIds.contains(id)) { loggedIds.append(id); targets.append(getTargetConfigById(id)); @@ -179,7 +179,7 @@ bool ExperimentConfig::validate(bool throwException) const { Array sessionTargetIds; // Build a list of target ids used in this session for (TrialConfig trial : session.trials) { - for (String id : trial.ids) { if (!sessionTargetIds.contains(id)) sessionTargetIds.append(id); } + for (String id : trial.targetIds) { if (!sessionTargetIds.contains(id)) sessionTargetIds.append(id); } } // Check each ID against the experiment targets array for (String targetId : sessionTargetIds) { @@ -219,11 +219,11 @@ void ExperimentConfig::printToLog() const{ sess.id.c_str(), sess.render.frameRate, sess.render.frameDelay); // Now iterate through each run for (int j = 0; j < sess.trials.size(); j++) { - String ids; - for (String id : sess.trials[j].ids) { ids += format("%s, ", id.c_str()); } - if (ids.length() > 2) ids = ids.substr(0, ids.length() - 2); + String targetIds; + for (String id : sess.trials[j].targetIds) { targetIds += format("%s, ", id.c_str()); } + if (targetIds.length() > 2) targetIds = targetIds.substr(0, targetIds.length() - 2); logPrintf("\t\tTrial Run Config: IDs = [%s], Count = %d\n", - ids.c_str(), sess.trials[j].count); + targetIds.c_str(), sess.trials[j].count); } } // Iterate through trials and print them diff --git a/source/FpsConfig.cpp b/source/FpsConfig.cpp index fe65cfe4..4ab889af 100644 --- a/source/FpsConfig.cpp +++ b/source/FpsConfig.cpp @@ -672,7 +672,7 @@ Question::Question(const Any& any) { if (reader.getIfPresent("optionsPerRow", optionsPerRow)) { if (type == Type::Rating) { // Ratings will ignore the optionsPerRow (always uses 1 row) - logPrintf("WARNING: Specified \"optionsPerRow\" parameter is ignored when using a \"Rating\" type question. If you'd like to change the layout look into using a \"MultipleChoice\" question instead!"); + logPrintf("WARNING: Specified \"optionsPerRow\" parameter is ignored when using a \"Rating\" type question. If you'd like to change the layout look into using a \"MultipleChoice\" question instead!\n"); } } diff --git a/source/Session.cpp b/source/Session.cpp index eae70b44..b48f601c 100644 --- a/source/Session.cpp +++ b/source/Session.cpp @@ -42,12 +42,17 @@ TrialConfig::TrialConfig(const Any& any) : FpsConfig(any, defaultConfig()) { switch (settingsVersion) { case 1: reader.getIfPresent("id", id); - reader.get("ids", ids, "An \"ids\" field must be provided for each set of trials!"); + if (!reader.getIfPresent("targetIds", targetIds)) { + if (reader.getIfPresent("ids", targetIds)) { + logPrintf("WARNING: Trial-level target IDs should be specified using \"targetIds\", the \"ids\" parameter is depricated!\n"); + } + else throw "The \"targetIds\" field must be provided for each set of trials!"; + } if (!reader.getIfPresent("count", count)) { count = defaultCount; } if (count < 1) { - throw format("Trial count < 1 not allowed! (%d count for trial with targets: %s)", count, Any(ids).unparse()); + throw format("Trial count < 1 not allowed! (%d count for trial with targets: %s)", count, Any(targetIds).unparse()); } break; default: @@ -59,7 +64,7 @@ TrialConfig::TrialConfig(const Any& any) : FpsConfig(any, defaultConfig()) { Any TrialConfig::toAny(const bool forceAll) const { Any a = FpsConfig::toAny(forceAll); a["id"] = id; - a["ids"] = ids; + a["targetIds"] = targetIds; a["count"] = count; return a; } @@ -127,13 +132,13 @@ float SessionConfig::getTrialsPerBlock(void) const { } Array SessionConfig::getUniqueTargetIds() const { - Array ids; + Array targetIds; for (TrialConfig trial : trials) { - for (String id : trial.ids) { - if (!ids.contains(id)) { ids.append(id); } + for (String id : trial.targetIds) { + if (!targetIds.contains(id)) { targetIds.append(id); } } } - return ids; + return targetIds; } Session::Session(FPSciApp* app, shared_ptr config) : m_app(app), m_sessConfig(config), m_weapon(app->weapon) { diff --git a/source/Session.h b/source/Session.h index 522b1ce2..b5ccfc1f 100644 --- a/source/Session.h +++ b/source/Session.h @@ -122,12 +122,12 @@ struct PlayerAction { class TrialConfig : public FpsConfig { public: String id; ///< Trial ID (used for logging) - Array ids; ///< Trial ID list + Array targetIds; ///< Trial ID list int count = 1; ///< Count of trials to be performed static int defaultCount; ///< Default count to use TrialConfig() : FpsConfig(defaultConfig()) {}; - TrialConfig(const Array& trialIds, int trialCount) : ids(trialIds), count(trialCount) {}; + TrialConfig(const Array& targetIds, int trialCount) : targetIds(targetIds), count(trialCount) {}; TrialConfig(const Any& any); static shared_ptr create() { return createShared(); } From 2915bbc89e63d211fe72405038f5d776650ae3bd Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Wed, 30 Nov 2022 14:27:53 -0500 Subject: [PATCH 27/38] Fix respawn w/o resetPlayerPositionBetweenTrials --- source/FPSciApp.cpp | 8 ++++---- source/FPSciApp.h | 2 +- source/Session.cpp | 4 +++- source/Session.h | 3 ++- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/source/FPSciApp.cpp b/source/FPSciApp.cpp index 879cb6ae..c2058f71 100644 --- a/source/FPSciApp.cpp +++ b/source/FPSciApp.cpp @@ -625,8 +625,8 @@ void FPSciApp::initPlayer(const shared_ptr config, const bool respawn if (respawn) player->respawn(); updateMouseSensitivity(); - // Set initial heading for session - sess->initialHeadingRadians = player->heading(); + // Set initial heading for trial/session (from config spawn heading) + sess->initialHeadingRadians = config->scene.spawnHeadingDeg * pif() / 180.f; } void FPSciApp::updateSession(const String& id, const bool forceSceneReload) { @@ -710,9 +710,9 @@ void FPSciApp::updateSession(const String& id, const bool forceSceneReload) { } } -void FPSciApp::updateTrial(const shared_ptr config, const bool forceSceneReload) { +void FPSciApp::updateTrial(const shared_ptr config, const bool forceSceneReload, const bool respawn) { trialConfig = config; // Naive way to store trial config pointer for now - updateConfigParameters(config, forceSceneReload); + updateConfigParameters(config, forceSceneReload, respawn); } void FPSciApp::updateConfigParameters(const shared_ptr config, const bool forceSceneReload, const bool respawn) { diff --git a/source/FPSciApp.h b/source/FPSciApp.h index e36faa1d..df01f699 100644 --- a/source/FPSciApp.h +++ b/source/FPSciApp.h @@ -257,7 +257,7 @@ class FPSciApp : public GApp { void markSessComplete(String id); /** Updates experiment state to the provided session id and updates player parameters (including mouse sensitivity) */ virtual void updateSession(const String& id, bool forceSceneReload = false); - void updateTrial(const shared_ptr config, const bool forceSceneReload = false); + void updateTrial(const shared_ptr config, const bool forceSceneReload = false, const bool respawn = false); void updateConfigParameters(const shared_ptr config, bool forceSceneReload = false, const bool respawn = false); void updateFrameParameters(int frameDelay, float frameRate); void updateTargetColor(const shared_ptr& target); diff --git a/source/Session.cpp b/source/Session.cpp index b48f601c..065d819b 100644 --- a/source/Session.cpp +++ b/source/Session.cpp @@ -206,7 +206,9 @@ bool Session::nextCondition() { // Get and update the trial configuration m_trialConfig = TrialConfig::createShared(m_sessConfig->trials[m_currTrialIdx]); - m_app->updateTrial(m_trialConfig); + // Respawn player for first trial in session (override session-level spawn position) + m_app->updateTrial(m_trialConfig, false, m_firstTrial); + if (m_firstTrial) m_firstTrial = false; // Produce (potentially random in range) pretrial duration if (isNaN(m_trialConfig->timing.pretrialDurationLambda)) m_pretrialDuration = m_trialConfig->timing.pretrialDuration; diff --git a/source/Session.h b/source/Session.h index b5ccfc1f..3234c57e 100644 --- a/source/Session.h +++ b/source/Session.h @@ -186,6 +186,7 @@ class Session : public ReferenceCountedObject { // Experiment management int m_destroyedTargets = 0; ///< Number of destroyed target int m_trialShotsHit = 0; ///< Count of total hits in this trial + bool m_firstTrial = true; ///< Is this the first trial in this session? bool m_hasSession; ///< Flag indicating whether psych helper has loaded a valid session int m_currBlock = 1; ///< Index to the current block of trials Array>> m_trials; ///< Storage for trials (to repeat over blocks) @@ -206,7 +207,7 @@ class Session : public ReferenceCountedObject { int m_currTrialIdx; ///< Current trial int m_currQuestionIdx = -1; ///< Current question index Array m_remainingTrials; ///< Completed flags - Array m_completedTrials; ///< Count of completed trials + Array m_completedTrials; ///< Count of completed trials Array>> m_targetConfigs; ///< Target configurations by trial // Time-based parameters From c575de0bba13a6714c0d27be13bc49cfb2dd569d Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Thu, 1 Dec 2022 10:09:58 -0500 Subject: [PATCH 28/38] Attempt to fix initialHeadingRadians bug --- source/FPSciApp.cpp | 15 ++++++--------- source/PlayerEntity.h | 1 + 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/source/FPSciApp.cpp b/source/FPSciApp.cpp index c2058f71..18bc9cc5 100644 --- a/source/FPSciApp.cpp +++ b/source/FPSciApp.cpp @@ -581,17 +581,14 @@ void FPSciApp::initPlayer(const shared_ptr config, const bool respawn player->setRespawnHeight(resetHeight); // Update the respawn heading - if (isnan(config->scene.spawnHeadingDeg)) { - if (setSpawnPosition) { // This is the first spawn in the scene - // No SceneConfig spawn heading specified, get heading from scene.Any player entity heading field + float spawnHeadingDeg = config->scene.spawnHeadingDeg; + if (isnan(spawnHeadingDeg)) { // No SceneConfig spawn heading specified, get heading from scene.Any player entity heading field + if (setSpawnPosition) { // This is the first spawn in the scene Point3 view_dir = playerCamera->frame().lookVector(); float spawnHeadingDeg = atan2(view_dir.x, -view_dir.z) * 180 / pif(); - player->setRespawnHeadingDegrees(spawnHeadingDeg); } } - else { // Respawn heading specified by the scene config - player->setRespawnHeadingDegrees(config->scene.spawnHeadingDeg); - } + player->setRespawnHeadingDegrees(spawnHeadingDeg); // Set player respawn location float respawnPosHeight = player->respawnPosHeight(); // Report the respawn position height @@ -625,8 +622,8 @@ void FPSciApp::initPlayer(const shared_ptr config, const bool respawn if (respawn) player->respawn(); updateMouseSensitivity(); - // Set initial heading for trial/session (from config spawn heading) - sess->initialHeadingRadians = config->scene.spawnHeadingDeg * pif() / 180.f; + // Set initial heading for trial/session reference target (from player spawn heading) + sess->initialHeadingRadians = player->respawnHeadingRadians(); } void FPSciApp::updateSession(const String& id, const bool forceSceneReload) { diff --git a/source/PlayerEntity.h b/source/PlayerEntity.h index 980549ce..96193ef5 100644 --- a/source/PlayerEntity.h +++ b/source/PlayerEntity.h @@ -125,6 +125,7 @@ class PlayerEntity : public VisibleEntity { float headTilt() const { return m_headTilt; } float heading() const { return m_headingRadians; } float respawnHeadingDeg() const { return m_spawnHeadingRadians * 180 / pif(); } + float respawnHeadingRadians() const { return m_spawnHeadingRadians; } /** For deserialization from Any / loading from file */ static shared_ptr create From 5168dd3cd220e1357efd965a75c8f092f712ade2 Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Thu, 1 Dec 2022 11:11:55 -0500 Subject: [PATCH 29/38] Fix scene comparison bug --- source/FpsConfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/FpsConfig.cpp b/source/FpsConfig.cpp index 4ab889af..43e636be 100644 --- a/source/FpsConfig.cpp +++ b/source/FpsConfig.cpp @@ -86,7 +86,7 @@ bool SceneConfig::operator!=(const SceneConfig& other) const { playerCamera != other.playerCamera || //gravity != other.gravity || (isnan(resetHeight) ? !isnan(other.resetHeight) : resetHeight != other.resetHeight) || - spawnPosition.isNaN() ? !other.spawnPosition.isNaN() : spawnPosition != other.spawnPosition || // Assume if any spawn coordinate is nan positions are equal + (spawnPosition.isNaN() ? !other.spawnPosition.isNaN() : spawnPosition != other.spawnPosition) || // Assume if any spawn coordinate is nan positions are equal (isnan(spawnHeadingDeg) ? !isnan(other.spawnHeadingDeg) : spawnHeadingDeg != other.spawnHeadingDeg); } From 7f0335a3b2377d63cf9bf7d8813629ca8dbae643 Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Thu, 1 Dec 2022 14:10:23 -0500 Subject: [PATCH 30/38] Fix NaN respawn heading bug that locked view --- source/FPSciApp.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/FPSciApp.cpp b/source/FPSciApp.cpp index 18bc9cc5..52f37b7a 100644 --- a/source/FPSciApp.cpp +++ b/source/FPSciApp.cpp @@ -585,10 +585,11 @@ void FPSciApp::initPlayer(const shared_ptr config, const bool respawn if (isnan(spawnHeadingDeg)) { // No SceneConfig spawn heading specified, get heading from scene.Any player entity heading field if (setSpawnPosition) { // This is the first spawn in the scene Point3 view_dir = playerCamera->frame().lookVector(); - float spawnHeadingDeg = atan2(view_dir.x, -view_dir.z) * 180 / pif(); + spawnHeadingDeg = atan2(view_dir.x, -view_dir.z) * 180 / pif(); + player->setRespawnHeadingDegrees(spawnHeadingDeg); } } - player->setRespawnHeadingDegrees(spawnHeadingDeg); + else player->setRespawnHeadingDegrees(spawnHeadingDeg); // Set player respawn location float respawnPosHeight = player->respawnPosHeight(); // Report the respawn position height From 560e69472802e07279f9187472e6a27e432457ca Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Thu, 1 Dec 2022 14:29:52 -0500 Subject: [PATCH 31/38] Update session-level members per trial --- source/Session.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/Session.cpp b/source/Session.cpp index 065d819b..37072d36 100644 --- a/source/Session.cpp +++ b/source/Session.cpp @@ -210,6 +210,11 @@ bool Session::nextCondition() { m_app->updateTrial(m_trialConfig, false, m_firstTrial); if (m_firstTrial) m_firstTrial = false; + // Update session fields (if changed) from trial + m_player = m_app->scene()->typedEntity("player"); + m_scene = m_app->scene().get(); + m_camera = m_app->activeCamera(); + // Produce (potentially random in range) pretrial duration if (isNaN(m_trialConfig->timing.pretrialDurationLambda)) m_pretrialDuration = m_trialConfig->timing.pretrialDuration; else m_pretrialDuration = drawTruncatedExp(m_trialConfig->timing.pretrialDurationLambda, m_trialConfig->timing.pretrialDurationRange[0], m_trialConfig->timing.pretrialDurationRange[1]); @@ -251,7 +256,6 @@ void Session::onInit(String filename, String description) { m_player = m_app->scene()->typedEntity("player"); m_scene = m_app->scene().get(); m_camera = m_app->activeCamera(); - m_targetModels = &(m_app->targetModels); // Check for valid session From a13eab10ce90a6b5304dba2e22e5efc9e878fb94 Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Thu, 1 Dec 2022 14:30:26 -0500 Subject: [PATCH 32/38] Only reload scenes at the trial-level --- source/FPSciApp.cpp | 74 ++++++++++++++++++++++----------------------- source/FPSciApp.h | 2 +- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/source/FPSciApp.cpp b/source/FPSciApp.cpp index 52f37b7a..4a0d9a6f 100644 --- a/source/FPSciApp.cpp +++ b/source/FPSciApp.cpp @@ -139,7 +139,6 @@ void FPSciApp::updateMouseSensitivity() { // rad/dot = rad/cm * cm/dot = 2PI / (cm/turn) * 2.54 / (dots/in) = (2.54 * 2PI)/ (DPI * cm/360) const double cmp360 = 36.0 / user->mouseDegPerMm; const double radiansPerDot = 2.0 * pi() * 2.54 / (cmp360 * user->mouseDPI); - const shared_ptr& fpm = dynamic_pointer_cast(cameraManipulator()); // Control player motion using the experiment config parameter shared_ptr player = scene()->typedEntity("player"); @@ -658,7 +657,7 @@ void FPSciApp::updateSession(const String& id, const bool forceSceneReload) { // Update the application w/ the session parameters updateUserMenu = true; if (!m_firstSession) m_showUserMenu = sessConfig->menu.showMenuBetweenSessions; - updateConfigParameters(sessConfig, forceSceneReload, true); + updateConfigParameters(sessConfig, forceSceneReload, true, false); // Handle results files const String resultsDirPath = startupConfig.experimentList[experimentIdx].resultsDirPath; @@ -713,7 +712,7 @@ void FPSciApp::updateTrial(const shared_ptr config, const bool forc updateConfigParameters(config, forceSceneReload, respawn); } -void FPSciApp::updateConfigParameters(const shared_ptr config, const bool forceSceneReload, const bool respawn) { +void FPSciApp::updateConfigParameters(const shared_ptr config, const bool forceSceneReload, const bool respawn, const bool trialLevel) { // Update reticle reticleConfig.index = config->reticle.indexSpecified ? config->reticle.index : currentUser()->reticle.index; reticleConfig.scale = config->reticle.scaleSpecified ? config->reticle.scale : currentUser()->reticle.scale; @@ -753,45 +752,46 @@ void FPSciApp::updateConfigParameters(const shared_ptr config, const m_combatFont = GFont::fromFile(System::findDataFile(config->targetView.combatTextFont)); // Load the experiment scene if we haven't already (target only) - if (config->scene.name.empty()) { - // No scene specified, load default scene - if (m_loadedScene.name.empty() || forceSceneReload) { - loadScene(m_defaultSceneName); // Note: this calls onGraphics() - m_loadedScene.name = m_defaultSceneName; + if (trialLevel) { // Only force scenes to load at the trial-level + if (config->scene.name.empty()) { + // No scene specified, load default scene + if (m_loadedScene.name.empty() || forceSceneReload) { + loadScene(m_defaultSceneName); // Note: this calls onGraphics() + m_loadedScene.name = m_defaultSceneName; + } + // Otherwise let the loaded scene persist + } + else if (config->scene != m_loadedScene || forceSceneReload) { + loadScene(config->scene.name); + m_loadedScene = config->scene; } - // Otherwise let the loaded scene persist - } - else if (config->scene != m_loadedScene || forceSceneReload) { - loadScene(config->scene.name); - m_loadedScene = config->scene; - } - // Player parameters - initPlayer(config, respawn); + initPlayer(config, respawn); // Setup the player within the scene - // Check for play mode specific parameters - if (notNull(weapon)) weapon->clearDecals(); - weapon->setConfig(&config->weapon); - weapon->setScene(scene()); - weapon->setCamera(activeCamera()); + // Check for play mode specific parameters + if (notNull(weapon)) weapon->clearDecals(); + weapon->setConfig(&config->weapon); + weapon->setScene(scene()); + weapon->setCamera(activeCamera()); - // Update weapon model (if drawn) and sounds - weapon->loadModels(); - weapon->loadSounds(); - if (!config->audio.sceneHitSound.empty()) { - m_sceneHitSound = Sound::create(System::findDataFile(config->audio.sceneHitSound)); - // Play silently to pre-load the sound - m_sceneHitSound->play(0.f); - } - if (!config->audio.refTargetHitSound.empty()) { - m_refTargetHitSound = Sound::create(System::findDataFile(config->audio.refTargetHitSound)); - // Play silently to pre-load the sound - m_refTargetHitSound->play(0.f); - } + // Update weapon model (if drawn) and sounds + weapon->loadModels(); + weapon->loadSounds(); + if (!config->audio.sceneHitSound.empty()) { + m_sceneHitSound = Sound::create(System::findDataFile(config->audio.sceneHitSound)); + // Play silently to pre-load the sound + m_sceneHitSound->play(0.f); + } + if (!config->audio.refTargetHitSound.empty()) { + m_refTargetHitSound = Sound::create(System::findDataFile(config->audio.refTargetHitSound)); + // Play silently to pre-load the sound + m_refTargetHitSound->play(0.f); + } - // Load static HUD textures - for (StaticHudElement element : config->hud.staticElements) { - hudTextures.set(element.filename, Texture::fromFile(System::findDataFile(element.filename))); + // Load static HUD textures + for (StaticHudElement element : config->hud.staticElements) { + hudTextures.set(element.filename, Texture::fromFile(System::findDataFile(element.filename))); + } } } diff --git a/source/FPSciApp.h b/source/FPSciApp.h index df01f699..9db390a8 100644 --- a/source/FPSciApp.h +++ b/source/FPSciApp.h @@ -258,7 +258,7 @@ class FPSciApp : public GApp { /** Updates experiment state to the provided session id and updates player parameters (including mouse sensitivity) */ virtual void updateSession(const String& id, bool forceSceneReload = false); void updateTrial(const shared_ptr config, const bool forceSceneReload = false, const bool respawn = false); - void updateConfigParameters(const shared_ptr config, bool forceSceneReload = false, const bool respawn = false); + void updateConfigParameters(const shared_ptr config, bool forceSceneReload = false, const bool respawn = false, const bool trialLevel = true); void updateFrameParameters(int frameDelay, float frameRate); void updateTargetColor(const shared_ptr& target); void presentQuestion(Question question); From aa42b34fd3ed7d1a85236d25f41860564b473516 Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Wed, 7 Dec 2022 13:47:12 -0500 Subject: [PATCH 33/38] Fix autofire test (refer to trial config) --- tests/FPSciTests.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/FPSciTests.cpp b/tests/FPSciTests.cpp index 12ab2db2..731a5781 100644 --- a/tests/FPSciTests.cpp +++ b/tests/FPSciTests.cpp @@ -234,7 +234,7 @@ TEST_F(FPSciTests, InitialTestConditions) { ASSERT_TRUE(notNull(player)); // Default config properties - EXPECT_FALSE(s_app->sessConfig->weapon.autoFire); + EXPECT_FALSE(s_app->trialConfig->weapon.autoFire); } TEST_F(FPSciTests, UsingFixedTimestep) { @@ -471,9 +471,9 @@ TEST_F(FPSciTests, TestAutoFire) { const float damagePerFrame = 0.1f; const float firePeriod = (float)fixedTestDeltaTime() - 0.001f; - s_app->sessConfig->weapon.autoFire = true; - s_app->sessConfig->weapon.damagePerSecond = damagePerFrame / firePeriod; - s_app->sessConfig->weapon.firePeriod = firePeriod; + s_app->trialConfig->weapon.autoFire = true; + s_app->trialConfig->weapon.damagePerSecond = damagePerFrame / firePeriod; + s_app->trialConfig->weapon.firePeriod = firePeriod; s_app->oneFrame(); @@ -493,7 +493,7 @@ TEST_F(FPSciTests, TestAutoFire) { } EXPECT_TRUE(frontAlive) << "Low damage-per-second with auto-fire but target still died"; - s_app->sessConfig->weapon.autoFire = false; + s_app->trialConfig->weapon.autoFire = false; } From 41e0f7fd4f165bc19a202e5ff873b7b81f47debb Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Wed, 7 Dec 2022 14:29:41 -0500 Subject: [PATCH 34/38] Clear loaded scene name on experiment init --- source/FPSciApp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/source/FPSciApp.cpp b/source/FPSciApp.cpp index 4a0d9a6f..30208c6a 100644 --- a/source/FPSciApp.cpp +++ b/source/FPSciApp.cpp @@ -49,6 +49,7 @@ void FPSciApp::initExperiment(){ } // Setup the scene + m_loadedScene.name = ""; // Clear scene name here setScene(PhysicsScene::create(m_ambientOcclusion)); scene()->registerEntitySubclass("PlayerEntity", &PlayerEntity::create); // Register the player entity for creation scene()->registerEntitySubclass("FlyingEntity", &FlyingEntity::create); // Register the target entity for creation From 217f78ee872dcff1285e167ef8cb4dfe70ce6d81 Mon Sep 17 00:00:00 2001 From: Josef Spjut Date: Thu, 8 Dec 2022 09:05:42 -0500 Subject: [PATCH 35/38] Added trials sample --- data-files/samples/trials.Experiment.Any | 142 +++++++++++++++++++++++ data-files/samples/trials.Status.Any | 11 ++ scripts/package/fpsci_packager.sh | 2 + 3 files changed, 155 insertions(+) create mode 100644 data-files/samples/trials.Experiment.Any create mode 100644 data-files/samples/trials.Status.Any diff --git a/data-files/samples/trials.Experiment.Any b/data-files/samples/trials.Experiment.Any new file mode 100644 index 00000000..b73a709f --- /dev/null +++ b/data-files/samples/trials.Experiment.Any @@ -0,0 +1,142 @@ +{ + description = "trials"; + frameRate = 120; + showHUD = true; + renderWeaponStatus = true; + cooldownMode = "ring"; + cooldownColor = Color4(0.7,1.0,0.9,0.75); + + weapon = { + id = "2hit"; + firePeriod = 0.3; + damagePerSecond = 2; + autoFire = false; + }; + sessions = ( + { + // This session tests out different weapon cooldowns and cooldown indicators while maintaining 2 hits to kill + id = "1target_cd"; + description = "1 target cooldown"; + trials = ( + { + count = 2; + ids = ( "static"); + }, + { + count = 2; + ids = ( "static"); + cooldownMode = "box"; + }, + { + count = 2; + ids = ( "static"); + cooldownMode = "box"; + weapon = { + id = "2hit"; + firePeriod = 0.5; + damagePerSecond = 1.1; + autoFire = false; + }; + }, + { + count = 2; + ids = ( "static"); + cooldownMode = "box"; + weapon = { + id = "2hit"; + firePeriod = 0.5; + damagePerSecond = 1.1; + autoFire = false; + }; + }, + { + count = 2; + ids = ( "static"); + cooldownMode = "box"; + weapon = { + id = "2hit"; + firePeriod = 0.1; + damagePerSecond = 5.5; + autoFire = false; + }; + }, + { + count = 2; + ids = ( "static"); + cooldownMode = "box"; + weapon = { + id = "2hit"; + firePeriod = 0.1; + damagePerSecond = 5.5; + autoFire = false; + }; + }, + ); + + }, + { + // This one assumes the 120 Hz limits for the values below 8.33 ms and creates a nearly 2 frame stutter periodically for most trials + id = "2targets_frames"; + description = "2 targets frame timing"; + trials = ( + { + count = 5; + ids = ( "static", "static"); + }, + { + count = 5; + ids = ( "static", "static"); + frameTimeArray = (0.008, 0.016); + }, + { + count = 5; + ids = ( "static", "static"); + frameTimeArray = (0.008, 0.008, 0.008, 0.016); + }, + { + count = 5; + ids = ( "static", "static"); + frameTimeArray = (0.008, 0.008, 0.008, 0.008, 0.008, 0.008, 0.008, 0.016); + }, + ); + + }, + { + // This session varies the latency + id = "5targets_latency"; + description = "5 targets latency"; + trials = ( + { + count = 5; + ids = ( "static", "static", "static", "static", "static"); + }, + { + count = 5; + ids = ( "static", "static", "static", "static", "static"); + frameDelay = 1; + }, + { + count = 5; + ids = ( "static", "static", "static", "static", "static"); + frameDelay = 2; + }, + { + count = 5; + ids = ( "static", "static", "static", "static", "static"); + frameDelay = 4; + }, + ); + + }, + ); + + targets = ( + { + id = "static"; + destSpace = "player"; + speed = ( 0, 0 ); + visualSize = ( 0.05, 0.05 ); + }, + ); + +} \ No newline at end of file diff --git a/data-files/samples/trials.Status.Any b/data-files/samples/trials.Status.Any new file mode 100644 index 00000000..13018d0e --- /dev/null +++ b/data-files/samples/trials.Status.Any @@ -0,0 +1,11 @@ +{ + currentUser = "Sample User"; + sessions = ( "1target_cd", "2targets_frames", "5targets_latency"); + settingsVersion = 1; + users = ( + { + id = "Sample User"; + sessions = ( "1target_cd", "2targets_frames", "5targets_latency"); + } ); + +} \ No newline at end of file diff --git a/scripts/package/fpsci_packager.sh b/scripts/package/fpsci_packager.sh index a9cd5335..a6548c50 100644 --- a/scripts/package/fpsci_packager.sh +++ b/scripts/package/fpsci_packager.sh @@ -52,6 +52,8 @@ cp ./data-files/samples/weapons.Status.Any dist/samples/ cp ./data-files/samples/weapons.Experiment.Any dist/samples/ cp ./data-files/samples/targets.Status.Any dist/samples/ cp ./data-files/samples/targets.Experiment.Any dist/samples/ +cp ./data-files/samples/trials.Status.Any dist/samples/ +cp ./data-files/samples/trials.Experiment.Any dist/samples/ cp ./data-files/samples/spheres.Status.Any dist/samples/ cp ./data-files/samples/spheres.Experiment.Any dist/samples/ cp ./data-files/samples/sample.User.Any dist/samples/ From 1c3f4e34aa19bb58f175cb2662b95b2e7da87fdb Mon Sep 17 00:00:00 2001 From: Ben Boudaoud Date: Thu, 8 Dec 2022 13:26:28 -0500 Subject: [PATCH 36/38] Minor updates and fixes for trial-config sample --- data-files/samples/trials.Experiment.Any | 72 ++++++++++++++---------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/data-files/samples/trials.Experiment.Any b/data-files/samples/trials.Experiment.Any index b73a709f..6695dd84 100644 --- a/data-files/samples/trials.Experiment.Any +++ b/data-files/samples/trials.Experiment.Any @@ -1,10 +1,8 @@ { - description = "trials"; + description = "trial-level configuration example"; frameRate = 120; showHUD = true; renderWeaponStatus = true; - cooldownMode = "ring"; - cooldownColor = Color4(0.7,1.0,0.9,0.75); weapon = { id = "2hit"; @@ -12,90 +10,100 @@ damagePerSecond = 2; autoFire = false; }; + sessions = ( { // This session tests out different weapon cooldowns and cooldown indicators while maintaining 2 hits to kill id = "1target_cd"; description = "1 target cooldown"; + cooldownMode = "ring"; // Session-level config can still be used to set defaults for all trials + cooldownColor = Color4(0.7,1.0,0.9,0.75); trials = ( { + id = "ring"; count = 2; - ids = ( "static"); + targetIds = ( "static"); }, { + id = "box"; count = 2; - ids = ( "static"); + targetIds = ( "static"); cooldownMode = "box"; }, { + id = "box slow fire"; count = 2; - ids = ( "static"); + targetIds = ( "static"); cooldownMode = "box"; weapon = { - id = "2hit"; + id = "2hit_slow"; firePeriod = 0.5; damagePerSecond = 1.1; - autoFire = false; }; }, { + id = "ring slow fire"; count = 2; - ids = ( "static"); - cooldownMode = "box"; + targetIds = ( "static"); + cooldownMode = "ring"; // Can specify this here, but optional as its the default behavior specified at the session-level weapon = { - id = "2hit"; + id = "2hit_slow"; firePeriod = 0.5; damagePerSecond = 1.1; - autoFire = false; }; }, { + id = "box fast fire"; count = 2; - ids = ( "static"); + targetIds = ( "static"); cooldownMode = "box"; weapon = { - id = "2hit"; + id = "2hit_fast"; firePeriod = 0.1; damagePerSecond = 5.5; - autoFire = false; }; }, { + id = "ring fast fire"; count = 2; - ids = ( "static"); - cooldownMode = "box"; + targetIds = ( "static"); + cooldownMode = "ring"; weapon = { - id = "2hit"; + id = "2hit_fast"; firePeriod = 0.1; damagePerSecond = 5.5; - autoFire = false; }; }, ); }, { - // This one assumes the 120 Hz limits for the values below 8.33 ms and creates a nearly 2 frame stutter periodically for most trials + // This session assumes the 120 Hz limits for the values below 8.33 ms and creates a nearly 2 frame stutter periodically for most trials id = "2targets_frames"; - description = "2 targets frame timing"; + description = "2 targets frame timing"; + frameTimeArray = (0.008); // Set a default value and override in trials below trials = ( - { + { + id = "constant"; count = 5; - ids = ( "static", "static"); + targetIds = ( "static", "static"); }, { + id = "1:2"; count = 5; - ids = ( "static", "static"); + targetIds = ( "static", "static"); frameTimeArray = (0.008, 0.016); }, { + id = "1:4"; count = 5; - ids = ( "static", "static"); + targetIds = ( "static", "static"); frameTimeArray = (0.008, 0.008, 0.008, 0.016); }, { + id = "1:8"; count = 5; - ids = ( "static", "static"); + targetIds = ( "static", "static"); frameTimeArray = (0.008, 0.008, 0.008, 0.008, 0.008, 0.008, 0.008, 0.016); }, ); @@ -107,22 +115,26 @@ description = "5 targets latency"; trials = ( { + id = "0 delay"; count = 5; - ids = ( "static", "static", "static", "static", "static"); + targetIds = ( "static", "static", "static", "static", "static"); }, { + id = "1 delay"; count = 5; - ids = ( "static", "static", "static", "static", "static"); + targetIds = ( "static", "static", "static", "static", "static"); frameDelay = 1; }, { + id = "2 delay"; count = 5; - ids = ( "static", "static", "static", "static", "static"); + targetIds = ( "static", "static", "static", "static", "static"); frameDelay = 2; }, { + id = "4 delay"; count = 5; - ids = ( "static", "static", "static", "static", "static"); + targetIds = ( "static", "static", "static", "static", "static"); frameDelay = 4; }, ); From b12968647966a3d9cd3619fd30bb564e31769fe6 Mon Sep 17 00:00:00 2001 From: Josef Spjut Date: Thu, 8 Dec 2022 13:36:33 -0500 Subject: [PATCH 37/38] rename targetmodel sample in packager - bug from #400 --- scripts/package/fpsci_packager.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/package/fpsci_packager.sh b/scripts/package/fpsci_packager.sh index a6548c50..2b464c5e 100644 --- a/scripts/package/fpsci_packager.sh +++ b/scripts/package/fpsci_packager.sh @@ -52,10 +52,10 @@ cp ./data-files/samples/weapons.Status.Any dist/samples/ cp ./data-files/samples/weapons.Experiment.Any dist/samples/ cp ./data-files/samples/targets.Status.Any dist/samples/ cp ./data-files/samples/targets.Experiment.Any dist/samples/ +cp ./data-files/samples/targetmodels.Status.Any dist/samples/ +cp ./data-files/samples/targetmodels.Experiment.Any dist/samples/ cp ./data-files/samples/trials.Status.Any dist/samples/ cp ./data-files/samples/trials.Experiment.Any dist/samples/ -cp ./data-files/samples/spheres.Status.Any dist/samples/ -cp ./data-files/samples/spheres.Experiment.Any dist/samples/ cp ./data-files/samples/sample.User.Any dist/samples/ cp ./data-files/samples/sa2019_track.Status.Any dist/samples/ cp ./data-files/samples/sa2019_track.Experiment.Any dist/samples/ From 41d3c5f24c3bf8f597d1e8415697fdec4517411c Mon Sep 17 00:00:00 2001 From: Josef Spjut Date: Thu, 8 Dec 2022 13:46:53 -0500 Subject: [PATCH 38/38] Switch samples to use `targetIds` for trials --- data-files/samples/sa2019_1hit.Experiment.Any | 60 +++++++++---------- .../samples/sa2019_track.Experiment.Any | 36 +++++------ .../samples/targetmodels.Experiment.Any | 12 ++-- data-files/samples/targets.Experiment.Any | 16 ++--- data-files/samples/weapons.Experiment.Any | 12 ++-- docs/getting_started.md | 6 +- 6 files changed, 71 insertions(+), 71 deletions(-) diff --git a/data-files/samples/sa2019_1hit.Experiment.Any b/data-files/samples/sa2019_1hit.Experiment.Any index 6ed88892..3a0c4db1 100644 --- a/data-files/samples/sa2019_1hit.Experiment.Any +++ b/data-files/samples/sa2019_1hit.Experiment.Any @@ -65,11 +65,11 @@ frameDelay = 0; blockCount = 10; trials = ( - {ids = ("static"), count = 3}, - {ids = ("straight_fly"), count = 3}, - {ids = ("stray_fly_easy"), count = 4}, - {ids = ("stray_fly_hard"), count = 4}, - {ids = ("strafe_jump"), count = 8} + {targetIds = ("static"), count = 3}, + {targetIds = ("straight_fly"), count = 3}, + {targetIds = ("stray_fly_easy"), count = 4}, + {targetIds = ("stray_fly_hard"), count = 4}, + {targetIds = ("strafe_jump"), count = 8} ); }, { @@ -78,11 +78,11 @@ frameDelay = 2; blockCount = 10; trials = ( - {ids = ("static"), count = 3}, - {ids = ("straight_fly"), count = 3}, - {ids = ("stray_fly_easy"), count = 4}, - {ids = ("stray_fly_hard"), count = 4}, - {ids = ("strafe_jump"), count = 8} + {targetIds = ("static"), count = 3}, + {targetIds = ("straight_fly"), count = 3}, + {targetIds = ("stray_fly_easy"), count = 4}, + {targetIds = ("stray_fly_hard"), count = 4}, + {targetIds = ("strafe_jump"), count = 8} ); }, { @@ -91,11 +91,11 @@ frameDelay = 0; blockCount = 10; trials = ( - {ids = ("static"), count = 3}, - {ids = ("straight_fly"), count = 3}, - {ids = ("stray_fly_easy"), count = 4}, - {ids = ("stray_fly_hard"), count = 4}, - {ids = ("strafe_jump"), count = 8} + {targetIds = ("static"), count = 3}, + {targetIds = ("straight_fly"), count = 3}, + {targetIds = ("stray_fly_easy"), count = 4}, + {targetIds = ("stray_fly_hard"), count = 4}, + {targetIds = ("strafe_jump"), count = 8} ); }, { @@ -104,11 +104,11 @@ frameDelay = 4; blockCount = 10; trials = ( - {ids = ("static"), count = 3}, - {ids = ("straight_fly"), count = 3}, - {ids = ("stray_fly_easy"), count = 4}, - {ids = ("stray_fly_hard"), count = 4}, - {ids = ("strafe_jump"), count = 8} + {targetIds = ("static"), count = 3}, + {targetIds = ("straight_fly"), count = 3}, + {targetIds = ("stray_fly_easy"), count = 4}, + {targetIds = ("stray_fly_hard"), count = 4}, + {targetIds = ("strafe_jump"), count = 8} ); }, { @@ -117,11 +117,11 @@ frameDelay = 0; blockCount = 10; trials = ( - {ids = ("static"), count = 3}, - {ids = ("straight_fly"), count = 3}, - {ids = ("stray_fly_easy"), count = 4}, - {ids = ("stray_fly_hard"), count = 4}, - {ids = ("strafe_jump"), count = 8} + {targetIds = ("static"), count = 3}, + {targetIds = ("straight_fly"), count = 3}, + {targetIds = ("stray_fly_easy"), count = 4}, + {targetIds = ("stray_fly_hard"), count = 4}, + {targetIds = ("strafe_jump"), count = 8} ); }, { @@ -130,11 +130,11 @@ frameDelay = 8; blockCount = 10; trials = ( - {ids = ("static"), count = 3}, - {ids = ("straight_fly"), count = 3}, - {ids = ("stray_fly_easy"), count = 4}, - {ids = ("stray_fly_hard"), count = 4}, - {ids = ("strafe_jump"), count = 8} + {targetIds = ("static"), count = 3}, + {targetIds = ("straight_fly"), count = 3}, + {targetIds = ("stray_fly_easy"), count = 4}, + {targetIds = ("stray_fly_hard"), count = 4}, + {targetIds = ("strafe_jump"), count = 8} ); }, ); diff --git a/data-files/samples/sa2019_track.Experiment.Any b/data-files/samples/sa2019_track.Experiment.Any index ee46ada3..19e0e328 100644 --- a/data-files/samples/sa2019_track.Experiment.Any +++ b/data-files/samples/sa2019_track.Experiment.Any @@ -67,9 +67,9 @@ frameDelay = 0; blockCount = 10; trials = ( - {ids = ("straight_fly"), count = 2}, - {ids = ("stray_fly_easy"), count = 5}, - {ids = ("strafe_jump"), count = 5} + {targetIds = ("straight_fly"), count = 2}, + {targetIds = ("stray_fly_easy"), count = 5}, + {targetIds = ("strafe_jump"), count = 5} ); }, { @@ -78,9 +78,9 @@ frameDelay = 2; blockCount = 10; trials = ( - {ids = ("straight_fly"), count = 2}, - {ids = ("stray_fly_easy"), count = 5}, - {ids = ("strafe_jump"), count = 5} + {targetIds = ("straight_fly"), count = 2}, + {targetIds = ("stray_fly_easy"), count = 5}, + {targetIds = ("strafe_jump"), count = 5} ); }, { @@ -89,9 +89,9 @@ frameDelay = 0; blockCount = 10; trials = ( - {ids = ("straight_fly"), count = 2}, - {ids = ("stray_fly_easy"), count = 5}, - {ids = ("strafe_jump"), count = 5} + {targetIds = ("straight_fly"), count = 2}, + {targetIds = ("stray_fly_easy"), count = 5}, + {targetIds = ("strafe_jump"), count = 5} ); }, { @@ -100,9 +100,9 @@ frameDelay = 4; blockCount = 10; trials = ( - {ids = ("straight_fly"), count = 2}, - {ids = ("stray_fly_easy"), count = 5}, - {ids = ("strafe_jump"), count = 5} + {targetIds = ("straight_fly"), count = 2}, + {targetIds = ("stray_fly_easy"), count = 5}, + {targetIds = ("strafe_jump"), count = 5} ); }, { @@ -111,9 +111,9 @@ frameDelay = 0; blockCount = 10; trials = ( - {ids = ("straight_fly"), count = 2}, - {ids = ("stray_fly_easy"), count = 5}, - {ids = ("strafe_jump"), count = 5} + {targetIds = ("straight_fly"), count = 2}, + {targetIds = ("stray_fly_easy"), count = 5}, + {targetIds = ("strafe_jump"), count = 5} ); }, { @@ -122,9 +122,9 @@ frameDelay = 8; blockCount = 10; trials = ( - {ids = ("straight_fly"), count = 2}, - {ids = ("stray_fly_easy"), count = 5}, - {ids = ("strafe_jump"), count = 5} + {targetIds = ("straight_fly"), count = 2}, + {targetIds = ("stray_fly_easy"), count = 5}, + {targetIds = ("strafe_jump"), count = 5} ); }, ); diff --git a/data-files/samples/targetmodels.Experiment.Any b/data-files/samples/targetmodels.Experiment.Any index 5f7bc526..123073cb 100644 --- a/data-files/samples/targetmodels.Experiment.Any +++ b/data-files/samples/targetmodels.Experiment.Any @@ -19,7 +19,7 @@ trials = ( { count = 20; - ids = ( "ico"); + targetIds = ( "ico"); } ); @@ -30,7 +30,7 @@ trials = ( { count = 20; - ids = ( "low"); + targetIds = ( "low"); } ); @@ -41,7 +41,7 @@ trials = ( { count = 20; - ids = ( "mid", "mid"); + targetIds = ( "mid", "mid"); } ); @@ -52,7 +52,7 @@ trials = ( { count = 20; - ids = ( "high", "high"); + targetIds = ( "high", "high"); } ); @@ -63,7 +63,7 @@ trials = ( { count = 20; - ids = ( "cylinder", "cylinder"); + targetIds = ( "cylinder", "cylinder"); } ); @@ -74,7 +74,7 @@ trials = ( { count = 20; - ids = ( "ico", "low", "mid", "high", "cylinder"); + targetIds = ( "ico", "low", "mid", "high", "cylinder"); }, ); diff --git a/data-files/samples/targets.Experiment.Any b/data-files/samples/targets.Experiment.Any index 6242a946..27433143 100644 --- a/data-files/samples/targets.Experiment.Any +++ b/data-files/samples/targets.Experiment.Any @@ -19,7 +19,7 @@ trials = ( { count = 20; - ids = ( "static"); + targetIds = ( "static"); } ); @@ -30,7 +30,7 @@ trials = ( { count = 20; - ids = ( "static", "static"); + targetIds = ( "static", "static"); } ); @@ -41,7 +41,7 @@ trials = ( { count = 20; - ids = ( "static", "static", "static", "static", "static"); + targetIds = ( "static", "static", "static", "static", "static"); } ); @@ -52,7 +52,7 @@ trials = ( { count = 20; - ids = ( "moving"); + targetIds = ( "moving"); } ); @@ -63,7 +63,7 @@ trials = ( { count = 20; - ids = ( "jumping"); + targetIds = ( "jumping"); } ); }, @@ -73,15 +73,15 @@ trials = ( { count = 7; - ids = ( "static"); + targetIds = ( "static"); }, { count = 7; - ids = ( "moving"); + targetIds = ( "moving"); }, { count = 6; - ids = ( "jumping"); + targetIds = ( "jumping"); }, ); diff --git a/data-files/samples/weapons.Experiment.Any b/data-files/samples/weapons.Experiment.Any index 3855c454..37bbe743 100644 --- a/data-files/samples/weapons.Experiment.Any +++ b/data-files/samples/weapons.Experiment.Any @@ -12,7 +12,7 @@ trials = ( { count = 20; - ids = ( "static", "static", "moving"); + targetIds = ( "static", "static", "moving"); } ); weapon = { @@ -29,7 +29,7 @@ trials = ( { count = 20; - ids = ( "static", "static", "moving"); + targetIds = ( "static", "static", "moving"); } ); weapon = { @@ -46,7 +46,7 @@ trials = ( { count = 20; - ids = ( "static", "static", "moving"); + targetIds = ( "static", "static", "moving"); } ); weapon = { @@ -63,7 +63,7 @@ trials = ( { count = 20; - ids = ( "static", "static", "moving"); + targetIds = ( "static", "static", "moving"); } ); weapon = { @@ -80,7 +80,7 @@ trials = ( { count = 20; - ids = ( "static", "static", "moving"); + targetIds = ( "static", "static", "moving"); } ); renderWeaponStatus = false; @@ -103,7 +103,7 @@ trials = ( { count = 20; - ids = ( "static", "static", "moving"); + targetIds = ( "static", "static", "moving"); } ); weapon = { diff --git a/docs/getting_started.md b/docs/getting_started.md index 42a2f42e..c1c3f6af 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -60,10 +60,10 @@ The only _required_ field within any given session configuration is the `trials` id : "test_sess", description: "my first session", frameRate : 120, - trials: [ - ids: ["first_target", "first_target"], // Spawn two "first_target"s at a time + trials: [{ + targetIds: ["first_target", "first_target"], // Spawn two "first_target"s at a time count: 5 // Perform 5 of this trial - ] + }] } ] ```