From 79f42e35ce8b60f16b77082f6dab63ecf365f188 Mon Sep 17 00:00:00 2001 From: SquidDev Date: Fri, 7 Feb 2020 14:50:51 +0000 Subject: [PATCH] Add a timeout to websocket.receive - Move timer handling to IAPIEnvironment, rather than OSAPI. This means the environment is reset on startup/shutdown, much like normal APIs. - Websocket.receive now accepts an optional timetout (much like rednet.receive). This starts a timer, and waits for it to complete. Closes #201 --- .../core/apis/IAPIEnvironment.java | 6 ++ .../dan200/computercraft/core/apis/OSAPI.java | 70 +++--------------- .../apis/http/websocket/WebsocketHandle.java | 24 +++++++ .../computercraft/core/computer/Computer.java | 2 +- .../core/computer/ComputerExecutor.java | 2 + .../core/computer/Environment.java | 72 ++++++++++++++++++- 6 files changed, 111 insertions(+), 65 deletions(-) diff --git a/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java b/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java index 00b5ebf8d..0a4315960 100644 --- a/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java +++ b/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java @@ -18,6 +18,8 @@ import javax.annotation.Nullable; public interface IAPIEnvironment { + String TIMER_EVENT = "timer"; + @FunctionalInterface interface IPeripheralChangeListener { @@ -64,6 +66,10 @@ public interface IAPIEnvironment void setLabel( @Nullable String label ); + int startTimer( long ticks ); + + void cancelTimer( int id ); + void addTrackingChange( @Nonnull TrackingField field, long change ); default void addTrackingChange( @Nonnull TrackingField field ) diff --git a/src/main/java/dan200/computercraft/core/apis/OSAPI.java b/src/main/java/dan200/computercraft/core/apis/OSAPI.java index e1bc5c1ab..71577a127 100644 --- a/src/main/java/dan200/computercraft/core/apis/OSAPI.java +++ b/src/main/java/dan200/computercraft/core/apis/OSAPI.java @@ -9,6 +9,8 @@ import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.shared.util.StringUtil; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import javax.annotation.Nonnull; import java.time.Instant; @@ -24,24 +26,12 @@ public class OSAPI implements ILuaAPI { private IAPIEnvironment m_apiEnvironment; - private final Map m_timers; - private final Map m_alarms; + private final Int2ObjectMap m_alarms = new Int2ObjectOpenHashMap<>(); private int m_clock; private double m_time; private int m_day; - private int m_nextTimerToken; - private int m_nextAlarmToken; - - private static class Timer - { - int m_ticksLeft; - - Timer( int ticksLeft ) - { - m_ticksLeft = ticksLeft; - } - } + private int m_nextAlarmToken = 0; private static class Alarm implements Comparable { @@ -66,10 +56,6 @@ public class OSAPI implements ILuaAPI public OSAPI( IAPIEnvironment environment ) { m_apiEnvironment = environment; - m_nextTimerToken = 0; - m_nextAlarmToken = 0; - m_timers = new HashMap<>(); - m_alarms = new HashMap<>(); } // ILuaAPI implementation @@ -87,11 +73,6 @@ public class OSAPI implements ILuaAPI m_day = m_apiEnvironment.getComputerEnvironment().getDay(); m_clock = 0; - synchronized( m_timers ) - { - m_timers.clear(); - } - synchronized( m_alarms ) { m_alarms.clear(); @@ -101,26 +82,7 @@ public class OSAPI implements ILuaAPI @Override public void update() { - synchronized( m_timers ) - { - // Update the clock - m_clock++; - - // Countdown all of our active timers - Iterator> it = m_timers.entrySet().iterator(); - while( it.hasNext() ) - { - Map.Entry entry = it.next(); - Timer timer = entry.getValue(); - timer.m_ticksLeft--; - if( timer.m_ticksLeft <= 0 ) - { - // Queue the "timer" event - queueLuaEvent( "timer", new Object[] { entry.getKey() } ); - it.remove(); - } - } - } + m_clock++; // Wait for all of our alarms synchronized( m_alarms ) @@ -155,11 +117,6 @@ public class OSAPI implements ILuaAPI @Override public void shutdown() { - synchronized( m_timers ) - { - m_timers.clear(); - } - synchronized( m_alarms ) { m_alarms.clear(); @@ -229,11 +186,8 @@ public class OSAPI implements ILuaAPI { // startTimer double timer = getFiniteDouble( args, 0 ); - synchronized( m_timers ) - { - m_timers.put( m_nextTimerToken, new Timer( (int) Math.round( timer / 0.05 ) ) ); - return new Object[] { m_nextTimerToken++ }; - } + int id = m_apiEnvironment.startTimer( Math.round( timer / 0.05 ) ); + return new Object[] { id }; } case 2: { @@ -278,10 +232,7 @@ public class OSAPI implements ILuaAPI return null; } case 10: // clock - synchronized( m_timers ) - { - return new Object[] { m_clock * 0.05 }; - } + return new Object[] { m_clock * 0.05 }; case 11: { // time @@ -345,10 +296,7 @@ public class OSAPI implements ILuaAPI { // cancelTimer int token = getInt( args, 0 ); - synchronized( m_timers ) - { - m_timers.remove( token ); - } + m_apiEnvironment.cancelTimer( token ); return null; } case 14: diff --git a/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandle.java b/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandle.java index 976b526c2..8433d30c3 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandle.java +++ b/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandle.java @@ -7,6 +7,7 @@ package dan200.computercraft.core.apis.http.websocket; import com.google.common.base.Objects; import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.lua.ArgumentHelper; import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaObject; import dan200.computercraft.api.lua.LuaException; @@ -23,6 +24,7 @@ import java.io.Closeable; import java.util.Arrays; import static dan200.computercraft.api.lua.ArgumentHelper.optBoolean; +import static dan200.computercraft.core.apis.IAPIEnvironment.TIMER_EVENT; import static dan200.computercraft.core.apis.http.websocket.Websocket.CLOSE_EVENT; import static dan200.computercraft.core.apis.http.websocket.Websocket.MESSAGE_EVENT; @@ -53,7 +55,21 @@ public class WebsocketHandle implements ILuaObject, Closeable switch( method ) { case 0: // receive + { checkOpen(); + int timeoutId; + if( arguments.length <= 0 || arguments[0] == null ) + { + // We do this rather odd argument validation to ensure we can tell the difference between a + // negative timeout and an absent one. + timeoutId = -1; + } + else + { + double timeout = ArgumentHelper.getFiniteDouble( arguments, 0 ); + timeoutId = websocket.environment().startTimer( Math.round( timeout / 0.05 ) ); + } + while( true ) { Object[] event = context.pullEvent( null ); @@ -63,9 +79,17 @@ public class WebsocketHandle implements ILuaObject, Closeable } else if( event.length >= 2 && Objects.equal( event[0], CLOSE_EVENT ) && Objects.equal( event[1], websocket.address() ) && closed ) { + // If the socket is closed abort. + return null; + } + else if( event.length >= 2 && timeoutId != -1 && Objects.equal( event[0], TIMER_EVENT ) + && event[1] instanceof Number && ((Number) event[1]).intValue() == timeoutId ) + { + // If we received a matching timer event then abort. return null; } } + } case 1: // send { diff --git a/src/main/java/dan200/computercraft/core/computer/Computer.java b/src/main/java/dan200/computercraft/core/computer/Computer.java index cacb755d1..316917535 100644 --- a/src/main/java/dan200/computercraft/core/computer/Computer.java +++ b/src/main/java/dan200/computercraft/core/computer/Computer.java @@ -183,7 +183,7 @@ public class Computer executor.tick(); // Update the environment's internal state. - internalEnvironment.update(); + internalEnvironment.tick(); // Propagate the environment's output to the world. if( internalEnvironment.updateOutput() ) externalOutputChanged.set( true ); diff --git a/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java b/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java index 0da3b2d65..a50b390e4 100644 --- a/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java +++ b/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java @@ -435,6 +435,7 @@ final class ComputerExecutor } // Init APIs + computer.getEnvironment().reset(); for( ILuaAPI api : apis ) api.startup(); // Init lua @@ -478,6 +479,7 @@ final class ComputerExecutor // Shutdown our APIs for( ILuaAPI api : apis ) api.shutdown(); + computer.getEnvironment().reset(); // Unload filesystem if( fileSystem != null ) diff --git a/src/main/java/dan200/computercraft/core/computer/Environment.java b/src/main/java/dan200/computercraft/core/computer/Environment.java index 35a79ee4d..819997c92 100644 --- a/src/main/java/dan200/computercraft/core/computer/Environment.java +++ b/src/main/java/dan200/computercraft/core/computer/Environment.java @@ -5,6 +5,7 @@ */ package dan200.computercraft.core.computer; +import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IWorkMonitor; import dan200.computercraft.core.apis.IAPIEnvironment; @@ -12,9 +13,12 @@ import dan200.computercraft.core.filesystem.FileSystem; import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.tracking.Tracking; import dan200.computercraft.core.tracking.TrackingField; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import javax.annotation.Nonnull; import java.util.Arrays; +import java.util.Iterator; /** * Represents the "environment" that a {@link Computer} exists in. @@ -53,6 +57,9 @@ public final class Environment implements IAPIEnvironment private final IPeripheral[] peripherals = new IPeripheral[ComputerSide.COUNT]; private IPeripheralChangeListener peripheralListener = null; + private final Int2ObjectMap timers = new Int2ObjectOpenHashMap<>(); + private int nextTimerToken = 0; + Environment( Computer computer ) { this.computer = computer; @@ -198,17 +205,47 @@ public final class Environment implements IAPIEnvironment } /** - * Called on the main thread to update the internal state of the computer. + * Called when the computer starts up or shuts down, to reset any internal state. * - * This just queues a {@code redstone} event if the input has changed. + * @see ILuaAPI#startup() + * @see ILuaAPI#shutdown() */ - void update() + void reset() + { + synchronized( timers ) + { + timers.clear(); + } + } + + /** + * Called on the main thread to update the internal state of the computer. + */ + void tick() { if( inputChanged ) { inputChanged = false; queueEvent( "redstone", null ); } + + synchronized( timers ) + { + // Countdown all of our active timers + Iterator> it = timers.int2ObjectEntrySet().iterator(); + while( it.hasNext() ) + { + Int2ObjectMap.Entry entry = it.next(); + Timer timer = entry.getValue(); + timer.ticksLeft--; + if( timer.ticksLeft <= 0 ) + { + // Queue the "timer" event + queueEvent( TIMER_EVENT, new Object[] { entry.getIntKey() } ); + it.remove(); + } + } + } } /** @@ -303,9 +340,38 @@ public final class Environment implements IAPIEnvironment computer.setLabel( label ); } + @Override + public int startTimer( long ticks ) + { + synchronized( timers ) + { + timers.put( nextTimerToken, new Timer( ticks ) ); + return nextTimerToken++; + } + } + + @Override + public void cancelTimer( int id ) + { + synchronized( timers ) + { + timers.remove( id ); + } + } + @Override public void addTrackingChange( @Nonnull TrackingField field, long change ) { Tracking.addValue( computer, field, change ); } + + private static class Timer + { + long ticksLeft; + + Timer( long ticksLeft ) + { + this.ticksLeft = ticksLeft; + } + } }