y u no call back :(

This commit is contained in:
minjaesong
2024-07-06 02:04:27 +09:00
parent 68ed16aa5a
commit ff433703f4
10 changed files with 380 additions and 242 deletions

View File

@@ -1052,6 +1052,7 @@ public class App implements ApplicationListener {
printdbg("AppLoader-Static", "Screen before change: " + currentScreen.getClass().getCanonicalName());
currentScreen.hide();
MusicService.INSTANCE.leaveScene();
currentScreen.dispose();
}
else {

View File

@@ -1,9 +1,12 @@
package net.torvald.terrarum
import net.torvald.terrarum.audio.AudioBank
import net.torvald.terrarum.audio.AudioMixer.Companion.DEFAULT_FADEOUT_LEN
import net.torvald.terrarum.transaction.Transaction
import net.torvald.terrarum.transaction.TransactionListener
import net.torvald.terrarum.transaction.TransactionState
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference
/**
* To play the music, create a transaction then pass it to the `runTransaction(Transaction)`
@@ -12,18 +15,147 @@ import net.torvald.terrarum.transaction.TransactionState
*/
object MusicService : TransactionListener() {
var currentPlaylist: TerrarumMusicPlaylist? = null; private set
private val currentPlaylistReference = AtomicReference<TerrarumMusicPlaylist?>(null)
val currentPlaylist: TerrarumMusicPlaylist?; get() = currentPlaylistReference.get()
override fun getCurrentStatusForTransaction(): TransactionState {
return TransactionState(
mutableMapOf(
"currentPlaylist" to currentPlaylist
hashMapOf(
"currentPlaylist" to currentPlaylistReference.get()
)
)
}
override fun commitTransaction(state: TransactionState) {
this.currentPlaylist = state["currentPlaylist"] as TerrarumMusicPlaylist?
(state["currentPlaylist"] as TerrarumMusicPlaylist?).let {
this.currentPlaylistReference.set(it)
}
}
private const val STATE_INTERMISSION = 0
private const val STATE_FIREPLAY = 1
private const val STATE_PLAYING = 2
val currentPlaybackState = AtomicInteger(STATE_INTERMISSION)
private var waitAkku = 0f
private var waitTime = 10f
private fun enterSTATE_INTERMISSION(waitFor: Float) {
currentPlaybackState.set(STATE_INTERMISSION)
waitTime = waitFor
waitAkku = 0f
}
private fun enterSTATE_FIREPLAY() {
val state = currentPlaybackState.get()
if (state == STATE_FIREPLAY) throw IllegalStateException("Cannot change state FIREPLAY -> FIREPLAY")
if (state == STATE_PLAYING) throw IllegalStateException("Cannot change state PLAYING -> FIREPLAY")
waitAkku = 0f
currentPlaybackState.set(STATE_FIREPLAY)
}
private fun enterSTATE_PLAYING() {
val state = currentPlaybackState.get()
if (state == STATE_INTERMISSION) throw IllegalStateException("Cannot change state INTERMISSION -> PLAYING")
if (state == STATE_PLAYING) throw IllegalStateException("Cannot change state PLAYING -> PLAYING")
currentPlaybackState.set(STATE_PLAYING)
}
fun getRandomMusicInterval() = 20f + Math.random().toFloat() * 4f // longer gap (20s to 24s)
private fun enterIntermissionAndWaitForPlaylist() {
val time = when (currentPlaylist?.diskJockeyingMode ?: "intermittent") {
"intermittent" -> getRandomMusicInterval()
"continuous" -> 0f
else -> getRandomMusicInterval()
}
enterSTATE_INTERMISSION(time)
}
fun enterIntermission() {
enterSTATE_INTERMISSION(getRandomMusicInterval())
}
fun onMusicFinishing(audio: AudioBank) {
synchronized(this) {
enterIntermissionAndWaitForPlaylist()
}
}
private var playTransactionOngoing = false
fun update(delta: Float) {
when (currentPlaybackState.get()) {
STATE_FIREPLAY -> {
if (!playTransactionOngoing) {
playTransactionOngoing = true
MusicService.resumePlaylistPlayback(
/* onSuccess: () -> Unit */
{
runTransaction(object : Transaction {
override fun start(state: TransactionState) {
App.audioMixer.startMusic((state["currentPlaylist"] as TerrarumMusicPlaylist).getCurrent())
}
override fun onSuccess(state: TransactionState) {
enterSTATE_PLAYING()
}
override fun onFailure(e: Throwable, state: TransactionState) {
enterSTATE_INTERMISSION(getRandomMusicInterval()) // will try again after a random interval
}
})
},
/* onFailure: (Throwable) -> Unit */
{
enterSTATE_INTERMISSION(getRandomMusicInterval()) // will try again after a random interval
},
// onFinally: () -> Unit
{
playTransactionOngoing = false
}
)
}
}
STATE_PLAYING -> {
// onMusicFinishing() will be called when the music finishes; it's on the setOnCompletionListener
}
STATE_INTERMISSION -> {
waitAkku += delta
if (waitAkku >= waitTime && currentPlaylist != null) {
enterSTATE_FIREPLAY()
}
}
}
}
fun enterScene(id: String) {
synchronized(this) {
/*val playlist = when (id) {
"title" -> getTitlePlaylist()
"ingame" -> getIngameDefaultPlaylist()
else -> getIngameDefaultPlaylist()
}
putNewPlaylist(playlist) {
// after the fadeout, we'll...
enterSTATE_FIREPLAY()
}*/
stopPlaylistPlayback { }
}
}
fun leaveScene() {
synchronized(this) {
stopPlaylistPlayback {}
}
}
/**
@@ -32,6 +164,9 @@ object MusicService : TransactionListener() {
* be reset as soon as the transaction starts. Note that the resetting behaviour is NOT atomic. (the given
* playlist will stay in reset state even if the transaction fails)
*
* When the transaction was successful, the old playlist gets disposed of, then the songFinishedHook of
* the songs in the new playlist will be overwritten, before `onSuccess` is called.
*
* The old playlist will be disposed of if and only if the transaction was successful.
*
* @param playlist An instance of a [TerrarumMusicPlaylist] to be changed into
@@ -45,7 +180,6 @@ object MusicService : TransactionListener() {
override fun start(state: TransactionState) {
oldPlaylist = state["currentPlaylist"] as TerrarumMusicPlaylist?
if (oldPlaylist == playlist) return
playlist.reset()
@@ -64,15 +198,27 @@ object MusicService : TransactionListener() {
waitUntil { fadedOut }
}
else {
/* do nothing */
// put new playlist
state["currentPlaylist"] = playlist
}
}
override fun onSuccess(state: TransactionState) {
oldPlaylist?.dispose()
(state["currentPlaylist"] as TerrarumMusicPlaylist?)?.let {
it.musicList.forEach {
it.songFinishedHook = {
onMusicFinishing(it)
}
}
}
onSuccess()
}
override fun onFailure(e: Throwable, state: TransactionState) {}
override fun onFailure(e: Throwable, state: TransactionState) {
e.printStackTrace()
}
}
}
@@ -80,20 +226,30 @@ object MusicService : TransactionListener() {
return object : Transaction {
override fun start(state: TransactionState) {
var fadedOut = false
var err: Throwable? = null
// request fadeout
App.audioMixer.requestFadeOut(App.audioMixer.musicTrack) {
// callback: play next song in the playlist
App.audioMixer.startMusic((state["currentPlaylist"] as TerrarumMusicPlaylist).getNext())
fadedOut = true
try {
// callback: play next song in the playlist
// TODO queue the next song on the playlist, the actual playback will be done by the state machine update
fadedOut = true
}
catch (e: Throwable) {
err = e
}
}
waitUntil { fadedOut }
waitUntil { fadedOut || err != null }
if (err != null) throw err!!
}
override fun onSuccess(state: TransactionState) {
onSuccess()
}
override fun onFailure(e: Throwable, state: TransactionState) {}
override fun onFailure(e: Throwable, state: TransactionState) {
e.printStackTrace()
}
}
}
@@ -101,20 +257,30 @@ object MusicService : TransactionListener() {
return object : Transaction {
override fun start(state: TransactionState) {
var fadedOut = false
var err: Throwable? = null
// request fadeout
App.audioMixer.requestFadeOut(App.audioMixer.musicTrack) {
// callback: play prev song in the playlist
App.audioMixer.startMusic((state["currentPlaylist"] as TerrarumMusicPlaylist).getPrev())
fadedOut = true
try {
// callback: play prev song in the playlist
// TODO queue the prev song on the playlist, the actual playback will be done by the state machine update
fadedOut = true
}
catch (e: Throwable) {
err = e
}
}
waitUntil { fadedOut }
waitUntil { fadedOut || err != null }
if (err != null) throw err!!
}
override fun onSuccess(state: TransactionState) {
onSuccess()
}
override fun onFailure(e: Throwable, state: TransactionState) {}
override fun onFailure(e: Throwable, state: TransactionState) {
e.printStackTrace()
}
}
}
@@ -122,18 +288,29 @@ object MusicService : TransactionListener() {
return object : Transaction {
override fun start(state: TransactionState) {
var fadedOut = false
var err: Throwable? = null
// request fadeout
App.audioMixer.requestFadeOut(App.audioMixer.musicTrack) {
// callback: play prev song in the playlist
App.audioMixer.startMusic((state["currentPlaylist"] as TerrarumMusicPlaylist).getNthSong(index))
fadedOut = true
try {
// callback: play prev song in the playlist
// TODO queue the nth song on the playlist, the actual playback will be done by the state machine update
fadedOut = true
}
catch (e: Throwable) {
err = e
}
}
waitUntil { fadedOut }
waitUntil { fadedOut || err != null }
if (err != null) throw err!!
}
override fun onSuccess(state: TransactionState) { onSuccess() }
override fun onFailure(e: Throwable, state: TransactionState) {}
override fun onFailure(e: Throwable, state: TransactionState) {
e.printStackTrace()
}
}
}
@@ -147,21 +324,32 @@ object MusicService : TransactionListener() {
}
waitUntil { fadedOut }
enterSTATE_INTERMISSION(Float.POSITIVE_INFINITY)
}
override fun onSuccess(state: TransactionState) { onSuccess() }
override fun onFailure(e: Throwable, state: TransactionState) {}
override fun onSuccess(state: TransactionState) {
onSuccess()
}
override fun onFailure(e: Throwable, state: TransactionState) {
e.printStackTrace()
}
}
}
private fun createTransactionForPlaylistResume(onSuccess: () -> Unit): Transaction {
private fun createTransactionForPlaylistResume(onSuccess: () -> Unit, onFailure: (Throwable) -> Unit): Transaction {
return object : Transaction {
override fun start(state: TransactionState) {
App.audioMixer.startMusic((state["currentPlaylist"] as TerrarumMusicPlaylist).getCurrent())
enterSTATE_FIREPLAY()
}
override fun onSuccess(state: TransactionState) { onSuccess() }
override fun onFailure(e: Throwable, state: TransactionState) {}
override fun onSuccess(state: TransactionState) {
onSuccess()
}
override fun onFailure(e: Throwable, state: TransactionState) {
e.printStackTrace()
onFailure(e)
}
}
}
@@ -174,22 +362,44 @@ object MusicService : TransactionListener() {
return object : Transaction {
override fun start(state: TransactionState) {
var fadedOut = false
var err: Throwable? = null
println("createTransactionPausePlaylistForMusicalFixture start")
// request fadeout
App.audioMixer.requestFadeOut(App.audioMixer.musicTrack, DEFAULT_FADEOUT_LEN / 2.0) {
// callback: let the caller actually take care of playing the audio
action()
println("createTransactionPausePlaylistForMusicalFixture fadeout end")
try {
// callback: let the caller actually take care of playing the audio
action()
fadedOut = true
fadedOut = true
}
catch (e: Throwable) {
err = e
e.printStackTrace()
}
}
waitUntil { fadedOut }
waitUntil { fadedOut || err != null }
if (err != null) throw err!!
// enter intermission state
println("createTransactionPausePlaylistForMusicalFixture fadeout waiting end, entering INTERMISSION state")
enterSTATE_INTERMISSION(Float.POSITIVE_INFINITY)
// wait until the interjected music finishes
println("createTransactionPausePlaylistForMusicalFixture waiting for musicFinished()")
waitUntil { musicFinished() }
}
override fun onSuccess(state: TransactionState) { onSuccess() }
override fun onFailure(e: Throwable, state: TransactionState) { onFailure(e) }
override fun onSuccess(state: TransactionState) {
onSuccess()
enterSTATE_INTERMISSION(getRandomMusicInterval())
}
override fun onFailure(e: Throwable, state: TransactionState) {
e.printStackTrace()
onFailure(e)
enterSTATE_INTERMISSION(getRandomMusicInterval())
}
}
// note to self: wait() and notify() using a lock object is impractical as the Java thread can wake up
@@ -215,9 +425,11 @@ object MusicService : TransactionListener() {
runTransaction(createTransactionPlaylistChange(playlist, onSuccess), onFinally)
}
/** Normal playlist playback will resume after the transaction, after the onSuccess/onFailure */
fun playMusicalFixture(action: () -> Unit, musicFinished: () -> Boolean, onSuccess: () -> Unit, onFailure: (Throwable) -> Unit) {
runTransaction(createTransactionPausePlaylistForMusicalFixture(action, musicFinished, onSuccess, onFailure))
}
/** Normal playlist playback will resume after the transaction, after the onSuccess/onFailure but before the onFinally */
fun playMusicalFixture(action: () -> Unit, musicFinished: () -> Boolean, onSuccess: () -> Unit, onFailure: (Throwable) -> Unit, onFinally: () -> Unit = {}) {
runTransaction(createTransactionPausePlaylistForMusicalFixture(action, musicFinished, onSuccess, onFailure), onFinally)
}
@@ -250,10 +462,10 @@ object MusicService : TransactionListener() {
runTransaction(createTransactionForPlaylistStop(onSuccess), onFinally)
}
fun resumePlaylistPlayback(onSuccess: () -> Unit) {
runTransaction(createTransactionForPlaylistResume(onSuccess))
fun resumePlaylistPlayback(onSuccess: () -> Unit, onFailure: (Throwable) -> Unit) {
runTransaction(createTransactionForPlaylistResume(onSuccess, onFailure))
}
fun resumePlaylistPlayback(onSuccess: () -> Unit, onFinally: () -> Unit) {
runTransaction(createTransactionForPlaylistResume(onSuccess), onFinally)
fun resumePlaylistPlayback(onSuccess: () -> Unit, onFailure: (Throwable) -> Unit, onFinally: () -> Unit) {
runTransaction(createTransactionForPlaylistResume(onSuccess, onFailure), onFinally)
}
}

View File

@@ -47,14 +47,19 @@ class TerrarumMusicPlaylist(
fun getCurrent(): MusicContainer {
checkRefill()
return musicList[currentIndexCursor]
return musicList[internalIndices[currentIndexCursor]]
}
fun getNext(): MusicContainer {
checkRefill()
currentIndexCursor += 1
return musicList[currentIndexCursor]
return musicList[internalIndices[currentIndexCursor]]
}
fun peekNext(): MusicContainer {
checkRefill()
return musicList[internalIndices[currentIndexCursor + 1]]
}
fun getPrev(): MusicContainer {
@@ -75,7 +80,7 @@ class TerrarumMusicPlaylist(
currentIndexCursor -= 1
return musicList[currentIndexCursor]
return musicList[internalIndices[currentIndexCursor]]
}
@@ -92,6 +97,8 @@ class TerrarumMusicPlaylist(
}
companion object {
private val validMusicExtensions = hashSetOf("mp3", "wav", "ogg")
/**
* Adding songFinishedHook to the songs is a responsibility of the caller.
*/
@@ -104,7 +111,7 @@ class TerrarumMusicPlaylist(
val playlistName = musicDir.substringAfterLast('/')
val playlist = File(musicDir).listFiles()?.sortedBy { it.name }?.mapNotNull {
val playlist = File(musicDir).listFiles()?.sortedBy { it.name }?.filter { Companion.validMusicExtensions.contains(it.extension.lowercase()) }?.mapNotNull {
printdbg(this, "Music: ${it.absolutePath}")
try {
MusicContainer(

View File

@@ -321,6 +321,8 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
)
loadedTime_t = App.getTIME_T()
MusicService.enterScene("ingame")
}
data class NewGameParams(
@@ -1007,6 +1009,7 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
}
musicStreamer.update(this, delta)
MusicService.update(delta)
////////////////////////
// ui-related updates //

View File

@@ -36,40 +36,6 @@ class TerrarumMusicStreamer : MusicStreamer() {
musicBin = ArrayList(if (shuffled) playlist.shuffled() else playlist.slice(playlist.indices))
}
/**
* Send the playlist to the MusicService to be played at the other play commands.
*
* @param musicDir where the music files are. Absolute path.
* @param shuffled if the tracks are to be shuffled
* @param diskJockeyingMode `intermittent` to give random gap between tracks, `continuous` for continuous playback
*/
fun queueDirectory(musicDir: String, shuffled: Boolean, diskJockeyingMode: String, playImmediately: Boolean, fileToName: ((String) -> String) = { name: String ->
name.substringBeforeLast('.').replace('_', ' ').split(" ").map { it.capitalize() }.joinToString(" ")
}): TerrarumMusicPlaylist {
// FIXME the olde way -- must be replaced with one that utilises MusicService
/*if (musicState != STATE_INIT && musicState != STATE_INTERMISSION) {
App.audioMixer.requestFadeOut(App.audioMixer.fadeBus, AudioMixer.DEFAULT_FADEOUT_LEN) // explicit call for fade-out when the game instance quits
stopMusic0(App.audioMixer.musicTrack.currentTrack)
}
playlist.forEach { it.tryDispose() }
registerSongsFromDir(musicDir, fileToName)
this.shuffled = shuffled
this.diskJockeyingMode = diskJockeyingMode
restockMusicBin()*/
val playlist = TerrarumMusicPlaylist.fromDirectory(musicDir, shuffled, diskJockeyingMode, fileToName)
if (!playImmediately)
MusicService.putNewPlaylist(playlist) {}
else
MusicService.putNewPlaylist(playlist)
return playlist
}
/**
* Adds a song to the head of the internal playlist (`musicBin`)
*/
@@ -142,7 +108,8 @@ class TerrarumMusicStreamer : MusicStreamer() {
private val musicStopHooks = ArrayList<(MusicContainer) -> Unit>()
init {
queueDirectory(App.customMusicDir, true, "intermittent", true)
// TODO queue and play the default playlist
// TerrarumMusicPlaylist.fromDirectory(App.defaultMusicDir, true, "intermittent")
}
@@ -175,78 +142,19 @@ class TerrarumMusicStreamer : MusicStreamer() {
protected var ambState = 0
protected var ambFired = false
fun getRandomMusicInterval() = 20f + Math.random().toFloat() * 4f // longer gap (20s to 24s)
fun getRandomMusicInterval() = MusicService.getRandomMusicInterval()
var stopCaller: Any? = null; private set
var playCaller: Any? = null; private set
var stopCallTime: Long? = null; private set
// call MusicService to fade out
// pauses playlist update
// called by MusicPlayerControl
fun stopMusicPlayback() {
private fun stopMusic0(song: AudioBank?, callStopMusicHook: Boolean = true, customPauseLen: Float? = null) {
musicState = if (customPauseLen == Float.POSITIVE_INFINITY) STATE_INIT else STATE_INTERMISSION
// printdbg(this, "stopMusic1 customLen=$customPauseLen, stateNow: $musicState, called by")
// printStackTrace(this)
intermissionAkku = 0f
intermissionLength = customPauseLen ?: getRandomMusicInterval()
musicFired = false
if (callStopMusicHook && musicStopHooks.isNotEmpty() && song is MusicContainer) musicStopHooks.forEach {
it(song)
}
// printdbg(this, "StopMusic Intermission: $intermissionLength seconds")
}
fun stopMusic(caller: Any?, callStopMusicHook: Boolean = true, pauseLen: Float = Float.POSITIVE_INFINITY) {
val timeNow = System.currentTimeMillis()
val trackThis = App.audioMixer.musicTrack.currentTrack
// resumes playlist update
// called by MusicPlayerControl
fun resumeMusicPlayback() {
if (caller is TerrarumMusicStreamer) {
if (stopCaller == null) {
// printdbg(this, "Caller: this, prev caller: $stopCaller, len: $pauseLen, obliging stop request")
stopMusic0(trackThis, callStopMusicHook, pauseLen)
}
else {
// printdbg(this, "Caller: this, prev caller: $stopCaller, len: $pauseLen, ignoring stop request")
}
}
else {
// printdbg(this, "Caller: $caller, prev caller: <doesn't matter>, len: $pauseLen, obliging stop request")
stopMusic0(trackThis, callStopMusicHook, pauseLen)
}
stopCaller = caller?.javaClass?.canonicalName
stopCallTime = System.currentTimeMillis()
// printStackTrace(this)
}
fun startMusic(caller: Any?) {
playCaller = caller
startMusic0(pullNextMusicTrack())
}
private fun startMusic0(song: MusicContainer) {
stopCaller = null
stopCallTime = null
App.audioMixer.startMusic(song)
// printdbg(this, "startMusic Now playing: ${song.name}, called by:")
// printStackTrace(this)
// INGAME.sendNotification("Now Playing $EMDASH ${song.name}")
if (musicStartHooks.isNotEmpty()) musicStartHooks.forEach { it(song) }
musicState = STATE_PLAYING
intermissionLength = 42.42424f
}
// MixerTrackProcessor will call this function externally to make gapless playback work
fun pullNextMusicTrack(callNextMusicHook: Boolean = false): MusicContainer {
// printStackTrace(this)
// prevent same song to play twice in row (for the most time)
if (musicBin.isEmpty()) {
restockMusicBin()
}
return musicBin.removeAt(0).also { mus ->
if (callNextMusicHook && musicStartHooks.isNotEmpty()) musicStartHooks.forEach { it(mus) }
}
}
private fun stopAmbient() {
@@ -286,12 +194,6 @@ class TerrarumMusicStreamer : MusicStreamer() {
override fun update(ingame: IngameInstance, delta: Float) {
val timeNow = System.currentTimeMillis()
val callerRecordExpired = (timeNow - (stopCallTime ?: 0L) > 1000)
if (callerRecordExpired && stopCaller != null) {
stopCaller = null
stopCallTime = null
}
// start the song queueing if there is one to play
if (firstTime) {
@@ -300,12 +202,21 @@ class TerrarumMusicStreamer : MusicStreamer() {
if (ambients.isNotEmpty()) ambState = STATE_INTERMISSION
}
when (musicState) {
STATE_FIREPLAY -> {
if (!musicFired) {
musicFired = true
startMusic0(pullNextMusicTrack())
MusicService.resumePlaylistPlayback(
// onSuccess: () -> Unit
{
musicFired = true
musicState = STATE_PLAYING
},
// onFailure: (Throwable) -> Unit
{
musicFired = false
musicState = STATE_INTERMISSION
},
)
}
}
STATE_PLAYING -> {
@@ -428,8 +339,6 @@ class TerrarumMusicStreamer : MusicStreamer() {
private val TRACK2_DAWN_ELEV_DN_MAX = -10.0
override fun dispose() {
App.audioMixer.requestFadeOut(App.audioMixer.fadeBus, AudioMixer.DEFAULT_FADEOUT_LEN) // explicit call for fade-out when the game instance quits
stopMusic0(App.audioMixer.musicTrack.currentTrack)
stopAmbient()
}
}

View File

@@ -304,6 +304,8 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
loadThingsWhileIntroIsVisible()
printdbg(this, "show() exit")
MusicService.enterScene("title")
}
@@ -330,6 +332,9 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
// update UIs //
uiContainer.forEach { it?.update(delta) }
// update MusicService //
MusicService.update(delta)
}
private val particles = CircularArray<ParticleBase>(16, true)

View File

@@ -103,11 +103,6 @@ class FixtureJukebox : Electric, PlaysMusic {
override fun updateImpl(delta: Float) {
super.updateImpl(delta)
// supress the normal background music playback
if (musicIsPlaying && !flagDespawn) {
(INGAME.musicStreamer as TerrarumMusicStreamer).stopMusic(this, true)
}
}
@@ -135,10 +130,8 @@ class FixtureJukebox : Electric, PlaysMusic {
}
musicNowPlaying = MusicContainer(title, musicFile.file()) {
returnToInitialState()
printdbg(this, "Stop music $title - $artist")
// can't call stopDiscPlayback() because of the recursion
(INGAME.musicStreamer as TerrarumMusicStreamer).stopMusic(this, pauseLen = (INGAME.musicStreamer as TerrarumMusicStreamer).getRandomMusicInterval())
}
discCurrentlyPlaying = index
@@ -149,24 +142,18 @@ class FixtureJukebox : Electric, PlaysMusic {
}*/
MusicService.playMusicalFixture(
// action: () -> Unit
{
/* action: () -> Unit */ {
startAudio(musicNowPlaying!!) { loadEffector(it) }
},
// musicFinished: () -> Boolean
{
/* musicFinished: () -> Boolean */ {
!musicIsPlaying
},
// onSuccess: () -> Unit
{
/* onSuccess: () -> Unit */ {
},
// onFailure: (Throwable) -> Unit
{
},
// onFinally: () -> Unit
returnToInitialState
/* onFailure: (Throwable) -> Unit */ {
returnToInitialState
}
)
@@ -185,8 +172,10 @@ class FixtureJukebox : Electric, PlaysMusic {
*/
fun stopGracefully() {
stopDiscPlayback()
(INGAME.musicStreamer as TerrarumMusicStreamer).stopMusic(this, pauseLen = (INGAME.musicStreamer as TerrarumMusicStreamer).getRandomMusicInterval())
try {
MusicService.enterIntermission()
}
catch (_: Throwable) {}
}
override fun drawBody(frameDelta: Float, batch: SpriteBatch) {

View File

@@ -92,11 +92,6 @@ class FixtureMusicalTurntable : Electric, PlaysMusic {
override fun updateImpl(delta: Float) {
super.updateImpl(delta)
// supress the normal background music playback
if (musicIsPlaying && !flagDespawn) {
(INGAME.musicStreamer as TerrarumMusicStreamer).stopMusic(this, true)
}
}
fun playDisc() {
@@ -118,10 +113,8 @@ class FixtureMusicalTurntable : Electric, PlaysMusic {
}
musicNowPlaying = MusicContainer(title, musicFile.file()) {
returnToInitialState()
App.printdbg(this, "Stop music $title - $artist")
// can't call stopDiscPlayback() because of the recursion
(INGAME.musicStreamer as TerrarumMusicStreamer).stopMusic(this, pauseLen = (INGAME.musicStreamer as TerrarumMusicStreamer).getRandomMusicInterval())
}
@@ -131,24 +124,19 @@ class FixtureMusicalTurntable : Electric, PlaysMusic {
}*/
MusicService.playMusicalFixture(
// action: () -> Unit
{
/* action: () -> Unit */ {
App.printdbg(this, "call startAudio(${musicNowPlaying?.name})")
startAudio(musicNowPlaying!!) { loadEffector(it) }
},
// musicFinished: () -> Boolean
{
/* musicFinished: () -> Boolean */ {
!musicIsPlaying
},
// onSuccess: () -> Unit
{
/* onSuccess: () -> Unit */ {
},
// onFailure: (Throwable) -> Unit
{
},
// onFinally: () -> Unit
returnToInitialState
/* onFailure: (Throwable) -> Unit */ {
returnToInitialState
}
)
@@ -162,8 +150,10 @@ class FixtureMusicalTurntable : Electric, PlaysMusic {
*/
fun stopGracefully() {
stopDiscPlayback()
(INGAME.musicStreamer as TerrarumMusicStreamer).stopMusic(this, pauseLen = (INGAME.musicStreamer as TerrarumMusicStreamer).getRandomMusicInterval())
try {
MusicService.enterIntermission()
}
catch (_: Throwable) {}
}
private fun stopDiscPlayback() {

View File

@@ -1,10 +1,12 @@
package net.torvald.terrarum.transaction
import net.torvald.terrarum.App.printdbg
import java.util.concurrent.atomic.AtomicReference
/**
* Created by minjaesong on 2024-06-28.
*/
interface Transaction {
/**
* Call this function to begin the transaction.
*
@@ -28,18 +30,23 @@ interface Transaction {
abstract class TransactionListener {
/** `null` if not locked, a class that acquired the lock if locked */
var transactionLockedBy: Any? = null; private set
val transactionLocked: Boolean; get() = (transactionLockedBy != null)
private val transactionLockingClass: AtomicReference<Transaction?> = AtomicReference(null)
val transactionLocked: Boolean; get() = (transactionLockingClass.get() != null)
/**
* Transaction modifies a given state to a new state, then applies the new state to the object.
* The given `transaction` may only modify values which is passed to it.
*
* Transaction is fully unlocked and the previous locker is unknowable by the time `onFinally` executes.
* Note that `onFinally` runs on the same thread the actual transaction has run (GL context not available).
*/
fun runTransaction(transaction: Transaction, onFinally: () -> Unit = {}) {
printdbg(this, "Accepting transaction $transaction")
Thread { synchronized(this) {
val state = getCurrentStatusForTransaction()
if (!transactionLocked) {
transactionLockingClass.set(transaction)
try {
transaction.start(state)
// if successful:
@@ -49,14 +56,17 @@ abstract class TransactionListener {
}
catch (e: Throwable) {
// if failed, notify the failure
System.err.println("Transaction failure: generic")
transaction.onFailure(e, state)
}
finally {
transactionLockingClass.set(null)
onFinally()
}
}
else {
transaction.onFailure(LockedException(this, transactionLockedBy), state)
System.err.println("Transaction failure: locked")
transaction.onFailure(LockedException(this, transactionLockingClass.get()), state)
}
} }.start()
}
@@ -65,10 +75,10 @@ abstract class TransactionListener {
protected abstract fun commitTransaction(state: TransactionState)
}
class LockedException(listener: TransactionListener, lockedBy: Any?) :
class LockedException(listener: TransactionListener, lockedBy: Transaction?) :
Exception("Transaction is rejected because the class '${listener.javaClass.canonicalName}' is locked by '${lockedBy?.javaClass?.canonicalName}'")
@JvmInline value class TransactionState(val valueTable: MutableMap<String, Any?>) {
@JvmInline value class TransactionState(val valueTable: HashMap<String, Any?>) {
operator fun get(key: String) = valueTable[key]
operator fun set(key: String, value: Any?) {
valueTable[key] = value