rough, this is a waste of tokens...
This commit is contained in:
@@ -64,26 +64,14 @@ public class BasePositionObject extends BaseObject {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getWidth() {
|
public int getWidth() {
|
||||||
float w = width.getFloatValue();
|
// A zero authored size means "use the image's natural size"; the
|
||||||
if (w == 0f) {
|
// renderer falls back to the decoded image dimensions when this is 0.
|
||||||
Object image = getImage();
|
return Math.round(width.getFloatValue() * scaleWidth.getFloatValue());
|
||||||
if (image == null) {
|
|
||||||
w = 0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Math.round(w * scaleWidth.getFloatValue());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getHeight() {
|
public int getHeight() {
|
||||||
float h = height.getFloatValue();
|
return Math.round(height.getFloatValue() * scaleHeight.getFloatValue());
|
||||||
if (h == 0f) {
|
|
||||||
Object image = getImage();
|
|
||||||
if (image == null) {
|
|
||||||
h = 0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Math.round(h * scaleHeight.getFloatValue());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -458,9 +458,23 @@ public final class VsmxInterpreter {
|
|||||||
|
|
||||||
/** Runs the script's main body, populating the given global variables object. */
|
/** Runs the script's main body, populating the given global variables object. */
|
||||||
public synchronized void run(VsmxObject globalVariables) {
|
public synchronized void run(VsmxObject globalVariables) {
|
||||||
|
// This can be invoked re-entrantly (Controller.changeResource loads and
|
||||||
|
// runs a new resource while the previous resource's script is still on
|
||||||
|
// the stack), so the outer execution state must be preserved and
|
||||||
|
// restored exactly like interpretFunction does. Without this, the
|
||||||
|
// nested run clobbers pc/exit/stack/callStates and the outer script
|
||||||
|
// crashes with a null operand stack after the nested run returns.
|
||||||
|
int savedPc = pc;
|
||||||
|
boolean savedExit = exit;
|
||||||
|
Deque<VsmxBaseObject> savedStack = stack;
|
||||||
|
Deque<VsmxCallState> savedCallStates = callStates;
|
||||||
|
VsmxCallState savedCallState = callState;
|
||||||
|
|
||||||
pc = 0;
|
pc = 0;
|
||||||
exit = false;
|
exit = false;
|
||||||
callStates = new ArrayDeque<>();
|
callStates = new ArrayDeque<>();
|
||||||
|
stack = null;
|
||||||
|
callState = null;
|
||||||
pushCallState(VsmxNull.singleton, 0, false, true);
|
pushCallState(VsmxNull.singleton, 0, false, true);
|
||||||
this.globalVariables = globalVariables;
|
this.globalVariables = globalVariables;
|
||||||
|
|
||||||
@@ -470,8 +484,16 @@ public final class VsmxInterpreter {
|
|||||||
|
|
||||||
interpret();
|
interpret();
|
||||||
|
|
||||||
callStates.clear();
|
pc = savedPc;
|
||||||
callState = null;
|
exit = savedExit;
|
||||||
|
stack = savedStack;
|
||||||
|
callStates = savedCallStates;
|
||||||
|
callState = savedCallState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Requests the currently-running script to stop after the current instruction. */
|
||||||
|
public void stop() {
|
||||||
|
exit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void callFunction(VsmxFunction function, VsmxBaseObject thisObject, VsmxBaseObject[] arguments,
|
private void callFunction(VsmxFunction function, VsmxBaseObject thisObject, VsmxBaseObject[] arguments,
|
||||||
|
|||||||
@@ -68,6 +68,10 @@ public class Controller extends BaseNativeObject {
|
|||||||
if (!resource.equals(resourceName)) {
|
if (!resource.equals(resourceName)) {
|
||||||
resource = resourceName;
|
resource = resourceName;
|
||||||
menuHost.changeResource(resourceName);
|
menuHost.changeResource(resourceName);
|
||||||
|
// The resource switch abandons this interpreter; stop the script
|
||||||
|
// that requested it so it does not keep running against a stale
|
||||||
|
// menu (the PSP drops the old resource's script at this point).
|
||||||
|
interpreter.stop();
|
||||||
}
|
}
|
||||||
return VsmxBoolean.getValue(true);
|
return VsmxBoolean.getValue(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public class GlobalVariables extends BaseNativeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void writeln(VsmxBaseObject object, VsmxBaseObject s1, VsmxBaseObject s2, VsmxBaseObject s3) {
|
public void writeln(VsmxBaseObject object, VsmxBaseObject s1, VsmxBaseObject s2, VsmxBaseObject s3) {
|
||||||
log.append(s1.getStringValue()).append(s3.getStringValue()).append('\n');
|
log.append(s1.getStringValue()).append(s2.getStringValue()).append(s3.getStringValue()).append('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
public VsmxBaseObject parseFloat(VsmxBaseObject object, VsmxBaseObject value) {
|
public VsmxBaseObject parseFloat(VsmxBaseObject object, VsmxBaseObject value) {
|
||||||
|
|||||||
@@ -76,7 +76,10 @@ public class Timer extends BaseNativeObject {
|
|||||||
if (due != null) {
|
if (due != null) {
|
||||||
if (due.function instanceof VsmxFunction f) {
|
if (due.function instanceof VsmxFunction f) {
|
||||||
synchronized (callbackLock) {
|
synchronized (callbackLock) {
|
||||||
interpreter.interpretFunction(f, due.object, due.params);
|
// Fire on the interpreter that compiled the callback so
|
||||||
|
// timers survive a resource switch (each resource owns
|
||||||
|
// its own interpreter).
|
||||||
|
f.getInterpreter().interpretFunction(f, due.object, due.params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -72,6 +72,17 @@ public final class PlayerApp extends Application {
|
|||||||
private volatile VsmxNativeObject controllerObject;
|
private volatile VsmxNativeObject controllerObject;
|
||||||
private long lastMenuKeyNanos = 0;
|
private long lastMenuKeyNanos = 0;
|
||||||
|
|
||||||
|
/** The open disc image, kept for the lifetime of the app so menu resources
|
||||||
|
* (RCO files) can be switched at runtime. */
|
||||||
|
private volatile UmdIsoReader iso;
|
||||||
|
private volatile String isoPath;
|
||||||
|
private volatile Playlist playlist;
|
||||||
|
/** The script global scope, shared across resource switches so globals
|
||||||
|
* (e.g. {@code g_nResChangeInfo}) survive. */
|
||||||
|
private volatile VsmxNativeObject globalVariables;
|
||||||
|
/** False until the first resource has run; later loads fire onChangeResource. */
|
||||||
|
private boolean resourceInitialized;
|
||||||
|
|
||||||
/** The active decode engine, closed when a new stream starts. */
|
/** The active decode engine, closed when a new stream starts. */
|
||||||
private volatile PlaybackEngine currentEngine;
|
private volatile PlaybackEngine currentEngine;
|
||||||
/** Bumped each time playback starts so stale streams stop themselves. */
|
/** Bumped each time playback starts so stale streams stop themselves. */
|
||||||
@@ -173,25 +184,55 @@ public final class PlayerApp extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadMenu(String isoPath) {
|
private void loadMenu(String isoPath) {
|
||||||
try (UmdIsoReader iso = new UmdIsoReader(Path.of(isoPath))) {
|
try {
|
||||||
Playlist playlist = Playlist.parse(iso);
|
this.iso = new UmdIsoReader(Path.of(isoPath));
|
||||||
byte[] rcoData = readFile(iso, "UMD_VIDEO/RESOURCE/EN100000.RCO");
|
this.isoPath = isoPath;
|
||||||
|
this.playlist = Playlist.parse(iso);
|
||||||
|
this.resourceInitialized = false;
|
||||||
|
loadResource("EN100000");
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Loads (or switches to) a menu resource and starts its script. */
|
||||||
|
private void loadResource(String resourceName) {
|
||||||
|
UmdIsoReader disc = iso;
|
||||||
|
if (disc == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] rcoData = readFile(disc, "UMD_VIDEO/RESOURCE/" + resourceName + ".RCO");
|
||||||
if (rcoData == null) {
|
if (rcoData == null) {
|
||||||
System.err.println("EN100000.RCO not found");
|
System.err.println("Menu resource not found: " + resourceName + ".RCO");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RcoFile rco = new RcoFile(rcoData);
|
RcoFile rco = new RcoFile(rcoData);
|
||||||
if (!rco.isValid()) {
|
if (!rco.isValid()) {
|
||||||
System.err.println("RCO invalid");
|
System.err.println("RCO invalid: " + resourceName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vsmx vsmx = new Vsmx(rco.vsmxData(), "main");
|
// Each resource gets a fresh interpreter so that switching
|
||||||
|
// resources never swaps the bytecode out from under an already
|
||||||
|
// running script (the old script keeps its own code and state).
|
||||||
VsmxInterpreter interpreter = new VsmxInterpreter();
|
VsmxInterpreter interpreter = new VsmxInterpreter();
|
||||||
|
Vsmx vsmx = new Vsmx(rco.vsmxData(), "main");
|
||||||
interpreter.setVsmx(vsmx);
|
interpreter.setVsmx(vsmx);
|
||||||
|
this.menuInterpreter = interpreter;
|
||||||
|
if (this.globalVariables == null) {
|
||||||
|
this.globalVariables = GlobalVariables.create(interpreter, SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (renderer == null) {
|
||||||
renderer = new RcoRenderer(menuCanvas);
|
renderer = new RcoRenderer(menuCanvas);
|
||||||
|
} else {
|
||||||
|
// The previous resource's object tree belongs to a different
|
||||||
|
// script; drop it before the new page tree is registered.
|
||||||
|
renderer.changeResource();
|
||||||
|
}
|
||||||
|
|
||||||
MenuHost menuHost = new MenuHost() {
|
MenuHost menuHost = new MenuHost() {
|
||||||
@Override
|
@Override
|
||||||
@@ -199,7 +240,7 @@ public final class PlayerApp extends Application {
|
|||||||
int audioFlag, int subtitleNumber, int subtitleFlag) {
|
int audioFlag, int subtitleNumber, int subtitleFlag) {
|
||||||
Playlist.Stream stream = playlist.streamForPlayListNumber(playListNumber);
|
Playlist.Stream stream = playlist.streamForPlayListNumber(playListNumber);
|
||||||
if (stream != null) {
|
if (stream != null) {
|
||||||
startPlayback(isoPath, stream, playListNumber);
|
startPlayback(PlayerApp.this.isoPath, stream, playListNumber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,26 +255,32 @@ public final class PlayerApp extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void changeResource(String resourceName) {
|
public void changeResource(String newResource) {
|
||||||
|
loadResource(newResource);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
VsmxNativeObject globals = GlobalVariables.create(interpreter, SCREEN_WIDTH, SCREEN_HEIGHT);
|
VsmxNativeObject controllerObject = Controller.create(interpreter, menuHost, resourceName);
|
||||||
VsmxNativeObject controllerObject = Controller.create(interpreter, menuHost, "EN100000");
|
|
||||||
VsmxNativeObject movieplayer = MoviePlayer.create(interpreter, menuHost, controllerObject);
|
VsmxNativeObject movieplayer = MoviePlayer.create(interpreter, menuHost, controllerObject);
|
||||||
VsmxNativeObject resource = Resource.create(interpreter, renderer, controllerObject, rco);
|
VsmxNativeObject resource = Resource.create(interpreter, renderer, controllerObject, rco);
|
||||||
VsmxNativeObject math = Math.create(interpreter);
|
VsmxNativeObject math = Math.create(interpreter);
|
||||||
globals.setPropertyValue(Controller.objectName, controllerObject);
|
globalVariables.setPropertyValue(Controller.objectName, controllerObject);
|
||||||
globals.setPropertyValue(MoviePlayer.objectName, movieplayer);
|
globalVariables.setPropertyValue(MoviePlayer.objectName, movieplayer);
|
||||||
globals.setPropertyValue(Resource.objectName, resource);
|
globalVariables.setPropertyValue(Resource.objectName, resource);
|
||||||
globals.setPropertyValue(Math.objectName, math);
|
globalVariables.setPropertyValue(Math.objectName, math);
|
||||||
|
|
||||||
|
interpreter.run(globalVariables);
|
||||||
|
|
||||||
interpreter.run(globals);
|
|
||||||
controller = (Controller) controllerObject.getObject();
|
controller = (Controller) controllerObject.getObject();
|
||||||
moviePlayer = (MoviePlayer) movieplayer.getObject();
|
moviePlayer = (MoviePlayer) movieplayer.getObject();
|
||||||
menuInterpreter = interpreter;
|
|
||||||
this.controllerObject = controllerObject;
|
this.controllerObject = controllerObject;
|
||||||
|
|
||||||
|
if (!resourceInitialized) {
|
||||||
|
resourceInitialized = true;
|
||||||
controller.callCallback(interpreter, "onAutoPlay", null);
|
controller.callCallback(interpreter, "onAutoPlay", null);
|
||||||
|
} else {
|
||||||
|
controller.callCallback(interpreter, "onChangeResource", null);
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import javafx.application.Platform;
|
|||||||
import javafx.scene.canvas.Canvas;
|
import javafx.scene.canvas.Canvas;
|
||||||
import javafx.scene.canvas.GraphicsContext;
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.WritableImage;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -158,10 +159,28 @@ public final class RcoRenderer implements Display {
|
|||||||
if (data == null || data.length == 0) {
|
if (data == null || data.length == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
// Decode synchronously through ImageIO. JavaFX's `new Image(InputStream)`
|
||||||
|
// loads in the background and `GraphicsContext.drawImage` silently skips
|
||||||
|
// images whose pixels have not been uploaded yet, which leaves the whole
|
||||||
|
// menu invisible even though the image headers are readable.
|
||||||
try (ByteArrayInputStream in = new ByteArrayInputStream(data)) {
|
try (ByteArrayInputStream in = new ByteArrayInputStream(data)) {
|
||||||
return new Image(in);
|
java.awt.image.BufferedImage buffered = javax.imageio.ImageIO.read(in);
|
||||||
|
if (buffered == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return toFxImage(buffered);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Image toFxImage(java.awt.image.BufferedImage buffered) {
|
||||||
|
int w = buffered.getWidth();
|
||||||
|
int h = buffered.getHeight();
|
||||||
|
int[] argb = new int[w * h];
|
||||||
|
buffered.getRGB(0, 0, w, h, argb, 0, w);
|
||||||
|
WritableImage image = new WritableImage(w, h);
|
||||||
|
image.getPixelWriter().setPixels(0, 0, w, h, javafx.scene.image.PixelFormat.getIntArgbInstance(), argb, 0, w);
|
||||||
|
return image;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user