Chart Seek Bugfix (#545)

* fix lag spike when starting playback from middle of chart

further optimization to GameManager which considerably reduces garbage generation

* let dsp offset be calculated on playback start if needed
This commit is contained in:
minenice55 2023-09-12 16:38:28 -04:00 committed by GitHub
parent 63a2814caa
commit 199d16cb9f
5 changed files with 90 additions and 37 deletions

View file

@ -170,11 +170,8 @@ namespace HeavenStudio
var chart = GameManager.instance.Beatmap; var chart = GameManager.instance.Beatmap;
double offset = chart.data.offset; double offset = chart.data.offset;
double dspTime = AudioSettings.dspTime; double dspTime = AudioSettings.dspTime;
absTimeAdjust = 0;
dspStart = dspTime;
startTime = DateTime.Now;
GameManager.instance.SortEventsList(); dspStart = dspTime;
startPos = GetSongPosFromBeat(beat); startPos = GetSongPosFromBeat(beat);
firstBeatOffset = offset; firstBeatOffset = offset;
@ -188,20 +185,21 @@ namespace HeavenStudio
{ {
musicScheduledTime = dspTime + musicStartDelay / SongPitch; musicScheduledTime = dspTime + musicStartDelay / SongPitch;
musicScheduledPitch = SongPitch; musicScheduledPitch = SongPitch;
musicSource.PlayScheduled(musicScheduledTime);
} }
else else
{ {
musicScheduledTime = dspTime; musicScheduledTime = dspTime;
musicScheduledPitch = SongPitch; musicScheduledPitch = SongPitch;
musicSource.Play();
} }
musicSource.PlayScheduled(musicScheduledTime);
} }
songPosBeat = GetBeatFromSongPos(time); songPosBeat = GetBeatFromSongPos(time);
startBeat = songPosBeat; startBeat = songPosBeat;
absTimeAdjust = 0;
startTime = DateTime.Now;
isPlaying = true; isPlaying = true;
isPaused = false; isPaused = false;
} }

View file

@ -108,6 +108,27 @@ namespace HeavenStudio
} }
} }
static bool StringStartsWith(string a, string b)
{
int aLen = a.Length;
int bLen = b.Length;
int ap = 0; int bp = 0;
while (ap < aLen && bp < bLen && a [ap] == b [bp])
{
ap++;
bp++;
}
return (bp == bLen);
}
public static bool IsGameSwitch(RiqEntity entity)
{
return StringStartsWith(entity.datamodel, "gameManager/switchGame");
}
public static List<RiqEntity> GetAllInGameManagerList(string gameName, string[] include) public static List<RiqEntity> GetAllInGameManagerList(string gameName, string[] include)
{ {
List<RiqEntity> temp1 = GameManager.instance.Beatmap.Entities.FindAll(c => c.datamodel.Split('/')[0] == gameName); List<RiqEntity> temp1 = GameManager.instance.Beatmap.Entities.FindAll(c => c.datamodel.Split('/')[0] == gameName);

View file

@ -55,6 +55,7 @@ namespace HeavenStudio
bool ChartLoadError; bool ChartLoadError;
List<double> eventBeats, tempoBeats, volumeBeats, sectionBeats; List<double> eventBeats, tempoBeats, volumeBeats, sectionBeats;
List<RiqEntity> allGameSwitches;
public event Action<double> onBeatChanged; public event Action<double> onBeatChanged;
public event Action<RiqEntity> onSectionChange; public event Action<RiqEntity> onSectionChange;
@ -163,8 +164,9 @@ namespace HeavenStudio
if (Beatmap.Entities.Count >= 1) if (Beatmap.Entities.Count >= 1)
{ {
SetCurrentGame(Beatmap.Entities[0].datamodel.Split(0)); string game = Beatmap.Entities[0].datamodel.Split(0);
SetGame(Beatmap.Entities[0].datamodel.Split(0)); SetCurrentGame(game);
SetGame(game);
} }
else else
{ {
@ -289,8 +291,9 @@ namespace HeavenStudio
if (Beatmap.Entities.Count >= 1) if (Beatmap.Entities.Count >= 1)
{ {
SetCurrentGame(Beatmap.Entities[0].datamodel.Split(0)); string game = Beatmap.Entities[0].datamodel.Split(0);
SetGame(Beatmap.Entities[0].datamodel.Split(0)); SetCurrentGame(game);
SetGame(game);
} }
else else
{ {
@ -330,26 +333,33 @@ namespace HeavenStudio
TimingAccuracyDisplay.instance.MakeAccuracyVfx(time, late); TimingAccuracyDisplay.instance.MakeAccuracyVfx(time, late);
} }
static bool StringStartsWith(string a, string b)
{
int aLen = a.Length;
int bLen = b.Length;
int ap = 0; int bp = 0;
while (ap < aLen && bp < bLen && a [ap] == b [bp])
{
ap++;
bp++;
}
return (bp == bLen);
}
public void SeekAheadAndPreload(double start, float seekTime = 8f) public void SeekAheadAndPreload(double start, float seekTime = 8f)
{ {
List<RiqEntity> entitiesAtSameBeat = ListPool<RiqEntity>.Get(); List<RiqEntity> entitiesAtSameBeat = ListPool<RiqEntity>.Get();
List<RiqEntity> gameSwitchs = ListPool<RiqEntity>.Get();
Minigames.Minigame inf; Minigames.Minigame inf;
//seek ahead to preload games that have assetbundles //seek ahead to preload games that have assetbundles
//check game switches first if (currentPreSwitch < allGameSwitches.Count && currentPreSwitch >= 0)
foreach (RiqEntity entity in Beatmap.Entities)
{ {
if (entity.datamodel.Split(1) == "switchGame") if (start + seekTime >= allGameSwitches[currentPreSwitch].beat)
{ {
gameSwitchs.Add(entity); string gameName = allGameSwitches[currentPreSwitch].datamodel.Split('/')[2];
}
}
if (currentPreSwitch < gameSwitchs.Count && currentPreSwitch >= 0)
{
if (start + seekTime >= gameSwitchs[currentPreSwitch].beat)
{
string gameName = gameSwitchs[currentPreSwitch].datamodel.Split(2);
inf = GetGameInfo(gameName); inf = GetGameInfo(gameName);
if (inf != null && inf.usesAssetBundle && !inf.AssetsLoaded) if (inf != null && inf.usesAssetBundle && !inf.AssetsLoaded)
{ {
@ -389,7 +399,6 @@ namespace HeavenStudio
} }
ListPool<RiqEntity>.Release(entitiesAtSameBeat); ListPool<RiqEntity>.Release(entitiesAtSameBeat);
ListPool<RiqEntity>.Release(gameSwitchs);
} }
public void SeekAheadAndDoPreEvent(double start) public void SeekAheadAndDoPreEvent(double start)
@ -408,8 +417,10 @@ namespace HeavenStudio
} }
SortEventsByPriority(entitiesAtSameBeat); SortEventsByPriority(entitiesAtSameBeat);
string[] seekEntityDatamodel = seekEntity.datamodel.Split('/');
float seekTime = eventCaller.GetGameAction( float seekTime = eventCaller.GetGameAction(
eventCaller.GetMinigame(seekEntity.datamodel.Split(0)), seekEntity.datamodel.Split(1)).preFunctionLength; eventCaller.GetMinigame(seekEntityDatamodel[0]), seekEntityDatamodel[1]).preFunctionLength;
if (start + seekTime >= eventBeats[currentPreSequence]) if (start + seekTime >= eventBeats[currentPreSequence])
{ {
@ -474,8 +485,7 @@ namespace HeavenStudio
float seekTime = 8f; float seekTime = 8f;
//seek ahead to preload games that have assetbundles //seek ahead to preload games that have assetbundles
SeekAheadAndPreload(cond.songPositionInBeatsAsDouble, seekTime); SeekAheadAndPreload(cond.songPositionInBeatsAsDouble, seekTime);
SeekAheadAndDoPreEvent(cond.songPositionInBeatsAsDouble);
SeekAheadAndDoPreEvent(Conductor.instance.songPositionInBeatsAsDouble);
if (currentEvent < Beatmap.Entities.Count && currentEvent >= 0) if (currentEvent < Beatmap.Entities.Count && currentEvent >= 0)
{ {
@ -592,7 +602,6 @@ namespace HeavenStudio
yield return new WaitForSeconds(delay); yield return new WaitForSeconds(delay);
bool paused = Conductor.instance.isPaused; bool paused = Conductor.instance.isPaused;
Conductor.instance.Play(beat);
if (paused) if (paused)
{ {
Util.SoundByte.UnpauseOneShots(); Util.SoundByte.UnpauseOneShots();
@ -604,11 +613,13 @@ namespace HeavenStudio
Conductor.instance.firstBeatOffset = Beatmap.data.offset; Conductor.instance.firstBeatOffset = Beatmap.data.offset;
SetCurrentEventToClosest(beat); SetCurrentEventToClosest(beat);
KillAllSounds(); KillAllSounds();
}
Minigame miniGame = currentGameO.GetComponent<Minigame>(); Minigame miniGame = currentGameO?.GetComponent<Minigame>();
if (miniGame != null) if (miniGame != null)
miniGame.OnPlay(beat); miniGame.OnPlay(beat);
}
Conductor.instance.Play(beat);
} }
public void Pause() public void Pause()
@ -700,16 +711,23 @@ namespace HeavenStudio
tempoBeats = Beatmap.TempoChanges.Select(c => c.beat).ToList(); tempoBeats = Beatmap.TempoChanges.Select(c => c.beat).ToList();
volumeBeats = Beatmap.VolumeChanges.Select(c => c.beat).ToList(); volumeBeats = Beatmap.VolumeChanges.Select(c => c.beat).ToList();
sectionBeats = Beatmap.SectionMarkers.Select(c => c.beat).ToList(); sectionBeats = Beatmap.SectionMarkers.Select(c => c.beat).ToList();
allGameSwitches = EventCaller.GetAllInGameManagerList("gameManager", new string[] { "switchGame" });
} }
void SortEventsByPriority(List<RiqEntity> entities) void SortEventsByPriority(List<RiqEntity> entities)
{ {
string[] xDatamodel;
string[] yDatamodel;
entities.Sort((x, y) => entities.Sort((x, y) =>
{ {
Minigames.Minigame xGame = eventCaller.GetMinigame(x.datamodel.Split(0)); xDatamodel = x.datamodel.Split('/');
Minigames.GameAction xAction = eventCaller.GetGameAction(xGame, x.datamodel.Split(1)); yDatamodel = y.datamodel.Split('/');
Minigames.Minigame yGame = eventCaller.GetMinigame(y.datamodel.Split(0));
Minigames.GameAction yAction = eventCaller.GetGameAction(yGame, y.datamodel.Split(1)); Minigames.Minigame xGame = eventCaller.GetMinigame(xDatamodel[0]);
Minigames.GameAction xAction = eventCaller.GetGameAction(xGame, xDatamodel[1]);
Minigames.Minigame yGame = eventCaller.GetMinigame(yDatamodel[0]);
Minigames.GameAction yAction = eventCaller.GetGameAction(yGame, yDatamodel[1]);
return yAction.priority.CompareTo(xAction.priority); return yAction.priority.CompareTo(xAction.priority);
}); });
@ -768,7 +786,7 @@ namespace HeavenStudio
currentPreEvent = GetIndexAfter(eventBeats, beat); currentPreEvent = GetIndexAfter(eventBeats, beat);
currentPreSequence = GetIndexAfter(eventBeats, beat); currentPreSequence = GetIndexAfter(eventBeats, beat);
var gameSwitchs = Beatmap.Entities.FindAll(c => c.datamodel.Split(1) == "switchGame"); var gameSwitchs = Beatmap.Entities.FindAll(c => c.datamodel.Split("/")[1] == "switchGame");
string newGame = Beatmap.Entities[Math.Min(currentEvent, eventBeats.Count - 1)].datamodel.Split(0); string newGame = Beatmap.Entities[Math.Min(currentEvent, eventBeats.Count - 1)].datamodel.Split(0);

View file

@ -8,6 +8,7 @@ using UnityEngine;
using HeavenStudio.InputSystem; using HeavenStudio.InputSystem;
using static JSL; using static JSL;
using HeavenStudio.Games;
namespace HeavenStudio.InputSystem namespace HeavenStudio.InputSystem
{ {
@ -186,6 +187,11 @@ namespace HeavenStudio
/*--------------------*/ /*--------------------*/
/* MAIN INPUT METHODS */ /* MAIN INPUT METHODS */
/*--------------------*/ /*--------------------*/
public static bool GetIsAction(string action)
{
return false;
}
// BUTTONS // BUTTONS
//TODO: refactor for controller and custom binds, currently uses temporary button checks //TODO: refactor for controller and custom binds, currently uses temporary button checks

View file

@ -501,6 +501,16 @@ namespace HeavenStudio
} }
} }
public class InputAction
{
public string name;
public List<InputActionEntry> mappings;
}
public class InputActionEntry
{
}
public delegate void EventCallback(); public delegate void EventCallback();
public delegate void ParamChangeCallback(string paramName, object paramValue, RiqEntity entity); public delegate void ParamChangeCallback(string paramName, object paramValue, RiqEntity entity);