1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-12-13 11:40:29 +00:00

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
This commit is contained in:
SquidDev 2020-02-07 14:50:51 +00:00
parent be89fc25f9
commit 79f42e35ce
6 changed files with 111 additions and 65 deletions

View File

@ -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 )

View File

@ -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<Integer, Timer> m_timers;
private final Map<Integer, Alarm> m_alarms;
private final Int2ObjectMap<Alarm> 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<Alarm>
{
@ -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<Map.Entry<Integer, Timer>> it = m_timers.entrySet().iterator();
while( it.hasNext() )
{
Map.Entry<Integer, Timer> 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:

View File

@ -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
{

View File

@ -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 );

View File

@ -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 )

View File

@ -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<Timer> 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<Int2ObjectMap.Entry<Timer>> it = timers.int2ObjectEntrySet().iterator();
while( it.hasNext() )
{
Int2ObjectMap.Entry<Timer> 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;
}
}
}