/// <summary> /// Starts playback of the provided script at the provided line and inline indexes. /// </summary> /// <param name="script">The script to play.</param> /// <param name="startLineIndex">Line index to start playback from.</param> /// <param name="startInlineIndex">Command inline index to start playback from.</param> public void Play(Script script, int startLineIndex = 0, int startInlineIndex = 0) { PlayedScript = script; if (Playlist is null || Playlist.ScriptName != script.Name) { Playlist = new ScriptPlaylist(script); } if (startLineIndex > 0 || startInlineIndex > 0) { var startAction = Playlist.GetFirstCommandAfterLine(startLineIndex, startInlineIndex); if (startAction is null) { Debug.LogError($"Script player failed to start: no commands found in script `{PlayedScript.Name}` at line #{startLineIndex}.{startInlineIndex}."); return; } PlayedIndex = Playlist.IndexOf(startAction); } else { PlayedIndex = 0; } Play(); }
private async void HandleButtonClicked() { button.interactable = false; if (!string.IsNullOrEmpty(scriptName)) { var script = await scriptManager.LoadScriptAsync(scriptName); if (script is null) { Debug.LogError($"Failed to play `{scriptName}` for the on-click script of button `{name}`. Make sure the specified script exists."); button.interactable = true; return; } await scriptPlayer.PreloadAndPlayAsync(script); } else if (!string.IsNullOrWhiteSpace(scriptText)) { var script = new Script(name, scriptText); var playlist = new ScriptPlaylist(script); foreach (var command in playlist) { await command.ExecuteAsync(); } } button.interactable = true; }
private async void PlayScriptAsync() { if (!string.IsNullOrEmpty(scriptName)) { var player = Engine.GetService <IScriptPlayer>(); if (player is null) { throw new Exception($"Failed to play a script via `{nameof(PlayScript)}` component attached to `{gameObject.name}` game object: script player service is not available."); } await player.PreloadAndPlayAsync(scriptName); return; } if (!string.IsNullOrWhiteSpace(scriptText)) { var text = string.IsNullOrEmpty(argument) ? scriptText : scriptText.Replace("{arg}", argument); var script = Script.FromScriptText($"`{name}` generated script", text); var playlist = new ScriptPlaylist(script); foreach (var command in playlist) { if (command.ShouldExecute) { if (!command.Wait && !(command is Commands.Command.IForceWait)) { command.ExecuteAsync().Forget(); } else { await command.ExecuteAsync(); } } } } }
private static async void HandleScriptModifiedAsync(string assetPath) { if (!Engine.Initialized || !(Engine.Behaviour is RuntimeBehaviour) || !configuration.HotReloadScripts || !ObjectUtils.IsValid(player.PlayedScript) || player.Playlist?.Count == 0) { return; } var scriptGuid = AssetDatabase.AssetPathToGUID(assetPath); var scriptName = editorResources.GetRecordByGuid(scriptGuid)?.Name; if (player.PlayedScript.Name != scriptName) { return; } var lastPlayedLineIndex = (player.PlayedCommand ?? player.Playlist.Last()).PlaybackSpot.LineIndex; // Find the first modified line in the updated script (before the played line). var rollbackIndex = -1; for (int i = 0; i < lastPlayedLineIndex; i++) { if (!player.PlayedScript.Lines.IsIndexValid(i)) // The updated script ends before the currently played line. { rollbackIndex = player.Playlist.GetCommandBeforeLine(i - 1, 0)?.PlaybackSpot.LineIndex ?? 0; break; } var oldLineHash = playedLineHashes[i]; var newLine = player.PlayedScript.Lines[i]; if (oldLineHash.EqualsFast(newLine.LineHash)) { continue; } rollbackIndex = player.Playlist.GetCommandBeforeLine(i, 0)?.PlaybackSpot.LineIndex ?? 0; break; } if (rollbackIndex > -1) // Script has changed before the played line. // Rollback to the line before the first modified one. { await stateManager.RollbackAsync(s => s.PlaybackSpot.LineIndex == rollbackIndex); } // Update the playlist and play. var resumeLineIndex = rollbackIndex > -1 ? rollbackIndex : lastPlayedLineIndex; var playlist = new ScriptPlaylist(player.PlayedScript, scriptManager); var playlistIndex = player.Playlist.FindIndex(c => c.PlaybackSpot.LineIndex == resumeLineIndex); player.Play(playlist, playlistIndex); if (player.WaitingForInput) { player.SetWaitingForInputEnabled(false); } }
public virtual async UniTask LoadServiceStateAsync(GameStateMap stateMap) { var state = stateMap.GetState <GameState>(); if (state is null) { ResetService(); return; } // Force stop and cancel all running commands to prevent state mutation while loading other services. Stop(); CancelCommands(); executedPlayedCommand = state.ExecutedPlayedCommand; if (state.Playing) // The playback is resumed (when necessary) after other services are loaded. { if (stateManager.RollbackInProgress) { stateManager.OnRollbackFinished += PlayAfterRollback; } else { stateManager.OnGameLoadFinished += PlayAfterLoad; } } if (state.GosubReturnSpots != null && state.GosubReturnSpots.Count > 0) { GosubReturnSpots = new Stack <PlaybackSpot>(state.GosubReturnSpots); } else { GosubReturnSpots.Clear(); } if (!string.IsNullOrEmpty(stateMap.PlaybackSpot.ScriptName)) { if (PlayedScript is null || !stateMap.PlaybackSpot.ScriptName.EqualsFast(PlayedScript.Name)) { PlayedScript = await scriptManager.LoadScriptAsync(stateMap.PlaybackSpot.ScriptName); Playlist = new ScriptPlaylist(PlayedScript, scriptManager); PlayedIndex = Playlist.IndexOf(stateMap.PlaybackSpot); Debug.Assert(PlayedIndex >= 0, $"Failed to load script player state: `{stateMap.PlaybackSpot}` doesn't exist in the current playlist."); var endIndex = providerConfig.ResourcePolicy == ResourcePolicy.Static ? Playlist.Count - 1 : Mathf.Min(PlayedIndex + providerConfig.DynamicPolicySteps, Playlist.Count - 1); await Playlist.PreloadResourcesAsync(PlayedIndex, endIndex); } else { PlayedIndex = Playlist.IndexOf(stateMap.PlaybackSpot); } }
/// <summary> /// Preloads the script's commands and starts playing. /// </summary> public async Task PreloadAndPlayAsync(Script script, int startLineIndex = 0, int startInlineIndex = 0) { Playlist = new ScriptPlaylist(script); var startAction = Playlist.GetFirstCommandAfterLine(startLineIndex, startInlineIndex); var startIndex = startAction != null?Playlist.IndexOf(startAction) : 0; var endIndex = providerManager.ResourcePolicy == ResourcePolicy.Static ? Playlist.Count - 1 : Mathf.Min(startIndex + providerManager.DynamicPolicySteps, Playlist.Count - 1); await Playlist.HoldResourcesAsync(startIndex, endIndex); Play(script, startLineIndex, startInlineIndex); }
private async UniTask <int> CountTotalCommandsAsync() { var result = 0; var scripts = await LoadAllScriptsAsync(); foreach (var script in scripts) { var playlist = new ScriptPlaylist(script.Name, script.ExtractCommands()); result += playlist.Count; } return(result); }
public async Task <int> UpdateTotalActionCountAsync() { TotalCommandsCount = 0; var scripts = await scriptManager.LoadAllScriptsAsync(); foreach (var script in scripts) { var playlist = new ScriptPlaylist(script); TotalCommandsCount += playlist.Count; } return(TotalCommandsCount); }
public async Task LoadServiceStateAsync(GameStateMap stateMap) { var state = stateMap.GetState <GameState>(); if (state is null) { ResetService(); return; } Stop(true); PlayedIndex = state.PlayedIndex; SetWaitingForInputActive(state.IsWaitingForInput); if (state.LastGosubReturnSpots != null && state.LastGosubReturnSpots.Count > 0) { LastGosubReturnSpots = new Stack <PlaybackSpot>(state.LastGosubReturnSpots); } else { LastGosubReturnSpots.Clear(); } if (!string.IsNullOrEmpty(state.PlayedScriptName)) { if (PlayedScript is null || !state.PlayedScriptName.EqualsFast(PlayedScript.Name)) { PlayedScript = await scriptManager.LoadScriptAsync(state.PlayedScriptName); Playlist = new ScriptPlaylist(PlayedScript); var endIndex = providerManager.ResourcePolicy == ResourcePolicy.Static ? Playlist.Count - 1 : Mathf.Min(PlayedIndex + providerManager.DynamicPolicySteps, Playlist.Count - 1); await Playlist.HoldResourcesAsync(PlayedIndex, endIndex); } // Start playback and force waiting for input to prevent looping same command when performing state rollback. if (stateManager.RollbackInProgress) { SetWaitingForInputActive(true); Play(); } } else { Playlist.Clear(); PlayedScript = null; } }
/// <summary> /// Performs hot-reload of the currently played script. /// </summary> public static async UniTask ReloadPlayedScriptAsync() { if (player?.Playlist is null || player.Playlist.Count == 0 || !player.PlayedScript) { Debug.LogError("Failed to perform hot reload: script player is not available or no script is currently played."); return; } var requireReload = string.IsNullOrEmpty(AssetDatabase.GetAssetPath(player.PlayedScript)); if (requireReload) // In case the played script is stored outside of Unity project, force reload it. { var prevLineHashes = playedLineHashes; // Otherwise they're overridden when playing the loaded script below. var scriptName = player.PlayedScript.Name; scriptManager.UnloadScript(scriptName); var script = await scriptManager.LoadScriptAsync(scriptName); player.Play(script, player.PlaybackSpot.LineIndex, player.PlaybackSpot.InlineIndex); playedLineHashes = prevLineHashes; } var lastPlayedLineIndex = (player.PlayedCommand ?? player.Playlist.Last()).PlaybackSpot.LineIndex; // Find the first modified line in the updated script (before the played line). var rollbackIndex = -1; for (int i = 0; i < lastPlayedLineIndex; i++) { if (!player.PlayedScript.Lines.IsIndexValid(i)) // The updated script ends before the currently played line. { rollbackIndex = player.Playlist.GetCommandBeforeLine(i - 1, 0)?.PlaybackSpot.LineIndex ?? 0; break; } var oldLineHash = playedLineHashes[i]; var newLine = player.PlayedScript.Lines[i]; if (oldLineHash.EqualsFast(newLine.LineHash)) { continue; } rollbackIndex = player.Playlist.GetCommandBeforeLine(i, 0)?.PlaybackSpot.LineIndex ?? 0; break; } if (rollbackIndex > -1) // Script has changed before the played line. // Rollback to the line before the first modified one. { await stateManager.RollbackAsync(s => s.PlaybackSpot.LineIndex == rollbackIndex); } // Update the playlist and play. var resumeLineIndex = rollbackIndex > -1 ? rollbackIndex : lastPlayedLineIndex; var playlist = new ScriptPlaylist(player.PlayedScript, scriptManager); var playlistIndex = player.Playlist.FindIndex(c => c.PlaybackSpot.LineIndex == resumeLineIndex); player.Play(playlist, playlistIndex); if (player.WaitingForInput) { player.SetWaitingForInputEnabled(false); } }