Proper Event System
ScriptCraft/pipeline/head This commit looks good Details

This commit is contained in:
TheBrokenRail 2020-05-04 20:36:32 -04:00
parent 23e335aa7a
commit d4a1ce6e8f
16 changed files with 306 additions and 84 deletions

View File

@ -7,13 +7,16 @@
"lib": ["es2020"],
"module": "es2020",
"target": "es2020",
"removeComments": true,
"typeRoots": ["node_modules/scriptcraft-api/types"],
"rootDir": "src",
"baseUrl": "src",
"paths": {
"minecraft": ["../node_modules/scriptcraft-api/lib/minecraft/index.d.ts"]
},
"outDir": "lib/ts"
"outDir": "lib/ts",
"moduleResolution": "node",
"resolveJsonModule": true
},
"include": [
"src/**/*"

View File

@ -305,7 +305,7 @@ static JSValue js_use_bridge(JSContext *ctx, JSValueConst this_val, int argc, JS
}
jclass clazz = (*env)->FindClass(env, "com/thebrokenrail/scriptcraft/core/ScriptCraftCore");
jmethodID methodID = (*env)->GetStaticMethodID(env, clazz, "useBridge", "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;");
jmethodID methodID = (*env)->GetStaticMethodID(env, clazz, "useBridgeNative", "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;");
const char *js_bridge_name = JS_ToCString(ctx, argv[0]);
jstring bridge_name = (*env)->NewStringUTF(env, js_bridge_name);

View File

@ -15,11 +15,6 @@ public class ScriptCraftAPI implements ScriptCraftEntrypoint {
return "index";
}
@Override
public boolean shouldAutoLoad() {
return false;
}
@Override
public void registerBridges() {
Bridges.init();

View File

@ -1,7 +1,7 @@
package com.thebrokenrail.scriptcraft.api.block;
import com.thebrokenrail.scriptcraft.core.ScriptCraftCore;
import com.thebrokenrail.scriptcraft.core.ValueUtil;
import com.thebrokenrail.scriptcraft.core.quickjs.QuickJSManager;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
@ -23,6 +23,6 @@ public class CustomBlock extends Block {
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
return ValueUtil.getEnumValue(ActionResult.class, (String) QuickJSManager.bridge("CustomBlock.onUse", id.toString(), world, state, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), hit.getSide().name(), player, hand.name()), ActionResult.PASS);
return ValueUtil.getEnumValue(ActionResult.class, (String) ScriptCraftCore.useBridge("CustomBlock.onUse", id.toString(), world, state, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), hit.getSide().name(), player, hand.name()), ActionResult.PASS);
}
}

View File

@ -1,5 +1,6 @@
package com.thebrokenrail.scriptcraft.api.block;
import com.thebrokenrail.scriptcraft.core.ScriptCraftCore;
import com.thebrokenrail.scriptcraft.core.quickjs.QuickJSManager;
import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable;
import net.minecraft.block.entity.BlockEntity;
@ -21,13 +22,13 @@ public class CustomBlockEntity extends BlockEntity implements BlockEntityClientS
objID = newObjID++;
QuickJSManager.bridge("CustomBlockEntity.create", id.toString(), objID);
ScriptCraftCore.useBridge("CustomBlockEntity.create", id.toString(), objID);
}
@Override
public void fromTag(CompoundTag tag) {
super.fromTag(tag);
QuickJSManager.bridge("CustomBlockEntity.fromTag", objID, tag);
ScriptCraftCore.useBridge("CustomBlockEntity.fromTag", objID, tag);
}
@Override
@ -37,7 +38,7 @@ public class CustomBlockEntity extends BlockEntity implements BlockEntityClientS
@Override
public CompoundTag toTag(CompoundTag tag) {
return (CompoundTag) QuickJSManager.bridge("CustomBlockEntity.toTag", objID, super.toTag(tag));
return (CompoundTag) ScriptCraftCore.useBridge("CustomBlockEntity.toTag", objID, super.toTag(tag));
}
@Override
@ -47,20 +48,20 @@ public class CustomBlockEntity extends BlockEntity implements BlockEntityClientS
@Override
public void tick() {
QuickJSManager.bridge("CustomBlockEntity.tick", objID);
ScriptCraftCore.useBridge("CustomBlockEntity.tick", objID);
}
@Override
public void setLocation(World world, BlockPos pos) {
super.setLocation(world, pos);
QuickJSManager.bridge("CustomBlockEntity.setLocation", objID, world, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ());
ScriptCraftCore.useBridge("CustomBlockEntity.setLocation", objID, world, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ());
}
@SuppressWarnings("deprecation")
@Override
protected void finalize() throws Throwable {
try {
QuickJSManager.bridge("CustomBlockEntity.free", objID);
ScriptCraftCore.useBridge("CustomBlockEntity.free", objID);
} finally {
super.finalize();
}

View File

@ -1,10 +1,24 @@
package com.thebrokenrail.scriptcraft.api.bridge;
import com.thebrokenrail.scriptcraft.core.ScriptCraftCore;
import com.thebrokenrail.scriptcraft.core.ValueUtil;
import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.fabricmc.fabric.api.event.player.UseItemCallback;
import net.fabricmc.fabric.api.event.world.WorldTickCallback;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResult;
import net.minecraft.util.TypedActionResult;
class EventBridges {
static void register() {
WorldTickCallback.EVENT.register(world -> ScriptCraftCore.useBridge("Event.tick", world));
WorldTickCallback.EVENT.register(world -> ScriptCraftCore.useBridge("Event.worldTick", world));
AttackBlockCallback.EVENT.register((playerEntity, world, hand, blockPos, direction) -> ValueUtil.getEnumValue(ActionResult.class, (String) ScriptCraftCore.useBridge("Event.attackBlock", playerEntity, world, hand.name(), (double) blockPos.getX(), (double) blockPos.getY(), (double) blockPos.getZ(), direction.name()), ActionResult.PASS));
UseBlockCallback.EVENT.register((playerEntity, world, hand, hitResult) -> ValueUtil.getEnumValue(ActionResult.class, (String) ScriptCraftCore.useBridge("Event.useBlock", playerEntity, world, hand.name(), (double) hitResult.getBlockPos().getX(), (double) hitResult.getBlockPos().getY(), (double) hitResult.getBlockPos().getZ(), hitResult.getSide().name()), ActionResult.PASS));
UseItemCallback.EVENT.register((playerEntity, world, hand) -> {
ActionResult result = ValueUtil.getEnumValue(ActionResult.class, (String) ScriptCraftCore.useBridge("Event.useItem", playerEntity, world, hand.name()), ActionResult.PASS);
ItemStack stack = playerEntity.getStackInHand(hand);
return new TypedActionResult<>(result, stack);
});
}
}

View File

@ -1,7 +1,7 @@
package com.thebrokenrail.scriptcraft.api.item;
import com.thebrokenrail.scriptcraft.core.ScriptCraftCore;
import com.thebrokenrail.scriptcraft.core.ValueUtil;
import com.thebrokenrail.scriptcraft.core.quickjs.QuickJSManager;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.Item;
@ -23,18 +23,18 @@ public class CustomItem extends Item {
@Override
public TypedActionResult<ItemStack> use(World world, PlayerEntity user, Hand hand) {
ActionResult result = ValueUtil.getEnumValue(ActionResult.class, (String) ScriptCraftCore.useBridge("CustomItem.onUse", id.toString(), world, user, hand.name()), ActionResult.PASS);
ItemStack stack = user.getStackInHand(hand);
ActionResult result = ValueUtil.getEnumValue(ActionResult.class, (String) QuickJSManager.bridge("CustomItem.onUse", id.toString(), world, user, hand.name()), ActionResult.PASS);
return new TypedActionResult<>(result, stack);
}
@Override
public ActionResult useOnBlock(ItemUsageContext context) {
return ValueUtil.getEnumValue(ActionResult.class, (String) QuickJSManager.bridge("CustomItem.onUseOnBlock", id.toString(), context.getWorld(), (double) context.getBlockPos().getX(), (double) context.getBlockPos().getY(), (double) context.getBlockPos().getZ(), context.getSide().name(), context.getPlayer(), context.getHand().name()), ActionResult.PASS);
return ValueUtil.getEnumValue(ActionResult.class, (String) ScriptCraftCore.useBridge("CustomItem.onUseOnBlock", id.toString(), context.getWorld(), (double) context.getBlockPos().getX(), (double) context.getBlockPos().getY(), (double) context.getBlockPos().getZ(), context.getSide().name(), context.getPlayer(), context.getHand().name()), ActionResult.PASS);
}
@Override
public boolean useOnEntity(ItemStack stack, PlayerEntity user, LivingEntity entity, Hand hand) {
return ValueUtil.toBoolean(QuickJSManager.bridge("CustomItem.onUseOnEntity", id.toString(), user, entity, hand.name()), false);
return ValueUtil.toBoolean(ScriptCraftCore.useBridge("CustomItem.onUseOnEntity", id.toString(), user, entity, hand.name()), false);
}
}

View File

@ -1,5 +1,6 @@
package com.thebrokenrail.scriptcraft.core;
import com.thebrokenrail.scriptcraft.core.quickjs.JSException;
import com.thebrokenrail.scriptcraft.core.quickjs.QuickJSNative;
import com.thebrokenrail.scriptcraft.core.quickjs.QuickJSManager;
import net.fabricmc.api.ModInitializer;
@ -18,7 +19,7 @@ public class ScriptCraftCore implements ModInitializer {
bridges.put(name, bridge);
}
public static Object useBridge(String name, Object... args) {
public static Object useBridgeNative(String name, Object... args) {
QuickJSManager.Task task = new QuickJSManager.Task() {
@Override
protected Object run(QuickJSNative quickjs) {
@ -32,6 +33,16 @@ public class ScriptCraftCore implements ModInitializer {
return QuickJSManager.sendTaskFromQuickJS(task);
}
public static Object useBridge(String method, Object... args) {
QuickJSManager.Task task = new QuickJSManager.Task() {
@Override
protected Object run(QuickJSNative quickjs) throws JSException {
return quickjs.bridge(method, args);
}
};
return QuickJSManager.sendTaskToQuickJS(task);
}
public static final String NAMESPACE = "scriptcraft";
public static final Pattern MOD_ID_PATTERN = Pattern.compile("[a-z][a-z0-9-_]{1,63}");
@ -45,9 +56,7 @@ public class ScriptCraftCore implements ModInitializer {
for (ScriptCraftEntrypoint mod : mods) {
if (MOD_ID_PATTERN.matcher(mod.getModID()).matches()) {
mod.registerBridges();
if (mod.shouldAutoLoad()) {
initializedMods.add(mod);
}
initializedMods.add(mod);
} else {
System.err.println("Invalid Mod ID: " + mod.getModID());
}

View File

@ -5,10 +5,6 @@ public interface ScriptCraftEntrypoint {
String getModIndex();
default boolean shouldAutoLoad() {
return true;
}
default void registerBridges() {
}
}

View File

@ -136,55 +136,51 @@ public class QuickJSManager {
}
}
private static final Object lock = new Object();
private static final AtomicBoolean lock = new AtomicBoolean(false);
private static synchronized Object sendTaskToQuickJS(Task task) {
public static synchronized Object sendTaskToQuickJS(Task task) {
if (!started.get()) {
return null;
}
synchronized (lock) {
quickJSOutputTask.set(null);
quickJSInputTask.set(task);
lock.notifyAll();
if (task.lastTask) {
return null;
if (lock.get()) {
throw new RuntimeException();
} else {
while (true) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
Task outputTask = quickJSOutputTask.get();
if (outputTask != null && !outputTask.done) {
lock.set(true);
quickJSOutputTask.set(null);
quickJSInputTask.set(task);
lock.notifyAll();
if (task.lastTask) {
return null;
} else {
while (true) {
try {
outputTask.obj = outputTask.run(null);
outputTask.err = false;
} catch (Throwable e) {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
outputTask.obj = null;
outputTask.err = true;
}
outputTask.done = true;
quickJSOutputTask.set(outputTask);
}
lock.notifyAll();
if (task.done) {
break;
Task outputTask = quickJSOutputTask.get();
if (outputTask != null && !outputTask.done) {
try {
outputTask.obj = outputTask.run(null);
outputTask.err = false;
} catch (Throwable e) {
e.printStackTrace();
outputTask.obj = null;
outputTask.err = true;
}
outputTask.done = true;
quickJSOutputTask.set(outputTask);
}
lock.notifyAll();
if (task.done) {
break;
}
}
}
lock.set(false);
return task.obj;
}
return task.obj;
}
}
public static Object bridge(String method, Object... args) {
Task task = new Task() {
@Override
protected Object run(QuickJSNative quickjs) throws JSException {
return quickjs.bridge(method, args);
}
};
return sendTaskToQuickJS(task);
}
}

View File

@ -3,15 +3,19 @@
*/
export enum ActionResult {
/**
* Do Nothing
* Indicates an action is not performed but allows other actions to perform.
*/
PASS = 'PASS',
/**
* Success
* Indicates an action is performed and the actor's hand should swing to indicate the performance.
*/
SUCCESS = 'SUCCESS',
/**
* Failure
* Indicates that an action is not performed and prevents other actions from performing.
*/
FAIL = 'FAIL',
/**
* Indicates an action is performed but no animation should accompany the performance.
*/
CONSUME = 'CONSUME'
}

View File

@ -1,35 +1,237 @@
import { World } from './world';
import { addBridge } from 'scriptcraft-core';
import { PlayerEntity } from './entity';
import { ActionResult, Hand, Pos, Direction } from './core';
/**
* Tick Event
* Event
*/
export class TickEvent {
/**
* Instance
*/
static INSTANCE: TickEvent = new TickEvent();
class Event<T, K> {
/**
* @internal
*/
#listeners: ((world: World) => void)[];
#listeners: ((obj: T) => K)[] = [];
/**
* @internal
*/
#defaultValue: K;
/**
* @internal
*/
constructor(defaultValue: K) {
this.#defaultValue = defaultValue;
}
/**
* Add Event Listener
* @param listener Event Listener
*/
addListener(listener: (world: World) => void): void {
addListener(listener: (obj: T) => K): void {
this.#listeners.push(listener);
}
/**
* Remove Event Listener
* @param listener Event Listener
*/
removeListener(listener: (obj: T) => K): void {
const index = this.#listeners.indexOf(listener, 0);
if (index > -1) {
this.#listeners.splice(index, 1);
}
}
/**
* @internal
*/
run(world: World): void {
run(obj: T): K {
for (const listener of this.#listeners) {
listener(world);
const value = listener(obj);
if (this.#defaultValue !== null && value != this.#defaultValue) {
return value;
}
}
return this.#defaultValue;
}
}
addBridge('Event.tick', (world: JavaObject) => TickEvent.INSTANCE.run(new World(world)));
/**
* Tick Event
*/
class WorldTickEvent {
/**
* @internal
*/
readonly #world: World;
/**
* Get World
* @returns World
*/
getWorld(): World {
return this.#world;
}
/**
* @internal
*/
constructor(world: World) {
this.#world = world;
}
}
/**
* Block Event
*/
class BlockEvent {
/**
* @internal
*/
readonly #world: World;
/**
* @internal
*/
readonly #player: PlayerEntity;
/**
* @internal
*/
readonly #hand: Hand;
/**
* @internal
*/
readonly #pos: Pos;
/**
* @internal
*/
readonly #side: Direction;
/**
* Get World
* @returns World
*/
getWorld(): World {
return this.#world;
}
/**
* Get Player
* @returns Player
*/
getPlayer(): PlayerEntity {
return this.#player;
}
/**
* Get Hand
* @returns Hand
*/
getHand(): Hand {
return this.#hand;
}
/**
* Get Block Position
* @returns Block Position
*/
getPos(): Pos {
return this.#pos;
}
/**
* Get Block Side
* @returns Block Side
*/
getSide(): Direction {
return this.#side;
}
/**
* @internal
*/
constructor(player: PlayerEntity, world: World, hand: Hand, pos: Pos, side: Direction) {
this.#player = player;
this.#world = world;
this.#hand = hand;
this.#pos = pos;
this.#side = side;
}
}
/**
* Item Event
*/
class ItemEvent {
/**
* @internal
*/
readonly #world: World;
/**
* @internal
*/
readonly #player: PlayerEntity;
/**
* @internal
*/
readonly #hand: Hand;
/**
* Get World
* @returns World
*/
getWorld(): World {
return this.#world;
}
/**
* Get Player
* @returns Player
*/
getPlayer(): PlayerEntity {
return this.#player;
}
/**
* Get Hand
* @returns Hand
*/
getHand(): Hand {
return this.#hand;
}
/**
* @internal
*/
constructor(player: PlayerEntity, world: World, hand: Hand) {
this.#player = player;
this.#world = world;
this.#hand = hand;
}
}
/**
* All Events
*/
export class Events {
/**
* World Tick Event
*/
static WORLD_TICK = new Event<WorldTickEvent, void>(null);
/**
* Attack Block Event
*/
static ATTACK_BLOCK = new Event<BlockEvent, ActionResult>(ActionResult.PASS);
/**
* Use Block Event
*/
static USE_BLOCK = new Event<BlockEvent, ActionResult>(ActionResult.PASS);
/**
* Use Item Event
*/
static USE_ITEM = new Event<ItemEvent, ActionResult>(ActionResult.PASS);
}
addBridge('Event.worldTick', (world: JavaObject) => Events.WORLD_TICK.run(new WorldTickEvent(new World(world))));
addBridge('Event.attackBlock', (player: JavaObject, world: JavaObject, hand: keyof typeof Hand, x: number, y: number, z: number, side: keyof typeof Direction) => Events.ATTACK_BLOCK.run(new BlockEvent(new PlayerEntity(player), new World(world), Hand[hand], new Pos(x, y, z), Direction[side])));
addBridge('Event.useBlock', (player: JavaObject, world: JavaObject, hand: keyof typeof Hand, x: number, y: number, z: number, side: keyof typeof Direction) => Events.USE_BLOCK.run(new BlockEvent(new PlayerEntity(player), new World(world), Hand[hand], new Pos(x, y, z), Direction[side])));
addBridge('Event.useItem', (player: JavaObject, world: JavaObject, hand: keyof typeof Hand) => Events.USE_ITEM.run(new ItemEvent(new PlayerEntity(player), new World(world), Hand[hand])));

View File

@ -10,10 +10,10 @@
export { Identifier, ActionResult, Hand, Pos, Direction, DirectionUtil } from './core';
export { CustomBlock, CustomBlockEntity, CustomBlockWithEntity, BlockSettings, BlockState, BlockEntity } from './block';
export { ItemStack, ItemSettings, CustomItem, BlockItem } from './item';
export { ItemStack, ItemSettings, CustomItem, BlockItem, ItemRarity } from './item';
export { World } from './world';
export { LivingEntity, PlayerEntity } from './entity';
export { CompoundTag, ListTag, NumberType } from './tag';
export { Registry } from './registry';
export { Inventory } from './inventory';
export { TickEvent } from './event';
export { Events } from './event';

View File

@ -161,7 +161,7 @@ export class ItemStack {
/**
* Item Rarity
*/
enum ItemRarity {
export enum ItemRarity {
COMMON = 'COMMON',
UNCOMMON = 'UNCOMMON',
RARE = 'RARE',

View File

@ -7,12 +7,15 @@
"lib": ["es2020"],
"module": "es2020",
"target": "es2020",
"removeComments": true,
"typeRoots": ["types"],
"rootDir": "src",
"baseUrl": "src",
"outDir": "lib/ts",
"declaration": true,
"declarationDir": "lib/dts"
"declarationDir": "lib/dts",
"moduleResolution": "node",
"resolveJsonModule": true
},
"include": [
"src/**/*"

View File

@ -3,7 +3,6 @@
"mode": "modules",
"readme": "none",
"excludeExternals": true,
"excludeNotExported": true,
"excludePrivate": true,
"stripInternal": true,
"out": "lib/typedoc"