441 lines
14 KiB
Java
441 lines
14 KiB
Java
/*
|
|
* This file is part of ComputerCraft - http://www.computercraft.info
|
|
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
|
|
* Send enquiries to dratcliffe@gmail.com
|
|
*/
|
|
|
|
package dan200.computercraft.core.apis;
|
|
|
|
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 javax.annotation.Nonnull;
|
|
import java.time.Instant;
|
|
import java.time.ZoneId;
|
|
import java.time.ZoneOffset;
|
|
import java.time.ZonedDateTime;
|
|
import java.time.format.DateTimeFormatterBuilder;
|
|
import java.util.*;
|
|
|
|
import static dan200.computercraft.core.apis.ArgumentHelper.*;
|
|
|
|
public class OSAPI implements ILuaAPI
|
|
{
|
|
private IAPIEnvironment m_apiEnvironment;
|
|
|
|
private final Map<Integer, Timer> m_timers;
|
|
private final Map<Integer, Alarm> m_alarms;
|
|
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 static class Alarm implements Comparable<Alarm>
|
|
{
|
|
final double m_time;
|
|
final int m_day;
|
|
|
|
Alarm( double time, int day )
|
|
{
|
|
m_time = time;
|
|
m_day = day;
|
|
}
|
|
|
|
@Override
|
|
public int compareTo( @Nonnull Alarm o )
|
|
{
|
|
double t = m_day * 24.0 + m_time;
|
|
double ot = m_day * 24.0 + m_time;
|
|
return Double.compare( t, ot );
|
|
}
|
|
}
|
|
|
|
public OSAPI( IAPIEnvironment environment )
|
|
{
|
|
m_apiEnvironment = environment;
|
|
m_nextTimerToken = 0;
|
|
m_nextAlarmToken = 0;
|
|
m_timers = new HashMap<>();
|
|
m_alarms = new HashMap<>();
|
|
}
|
|
|
|
// ILuaAPI implementation
|
|
|
|
@Override
|
|
public String[] getNames()
|
|
{
|
|
return new String[] { "os" };
|
|
}
|
|
|
|
@Override
|
|
public void startup()
|
|
{
|
|
m_time = m_apiEnvironment.getComputerEnvironment().getTimeOfDay();
|
|
m_day = m_apiEnvironment.getComputerEnvironment().getDay();
|
|
m_clock = 0;
|
|
|
|
synchronized( m_timers )
|
|
{
|
|
m_timers.clear();
|
|
}
|
|
|
|
synchronized( m_alarms )
|
|
{
|
|
m_alarms.clear();
|
|
}
|
|
}
|
|
|
|
@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();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Wait for all of our alarms
|
|
synchronized( m_alarms )
|
|
{
|
|
double previousTime = m_time;
|
|
int previousDay = m_day;
|
|
double time = m_apiEnvironment.getComputerEnvironment().getTimeOfDay();
|
|
int day = m_apiEnvironment.getComputerEnvironment().getDay();
|
|
|
|
if( time > previousTime || day > previousDay )
|
|
{
|
|
double now = m_day * 24.0 + m_time;
|
|
Iterator<Map.Entry<Integer, Alarm>> it = m_alarms.entrySet().iterator();
|
|
while( it.hasNext() )
|
|
{
|
|
Map.Entry<Integer, Alarm> entry = it.next();
|
|
Alarm alarm = entry.getValue();
|
|
double t = alarm.m_day * 24.0 + alarm.m_time;
|
|
if( now >= t )
|
|
{
|
|
queueLuaEvent( "alarm", new Object[] { entry.getKey() } );
|
|
it.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
m_time = time;
|
|
m_day = day;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void shutdown()
|
|
{
|
|
synchronized( m_timers )
|
|
{
|
|
m_timers.clear();
|
|
}
|
|
|
|
synchronized( m_alarms )
|
|
{
|
|
m_alarms.clear();
|
|
}
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public String[] getMethodNames()
|
|
{
|
|
return new String[] {
|
|
"queueEvent",
|
|
"startTimer",
|
|
"setAlarm",
|
|
"shutdown",
|
|
"reboot",
|
|
"computerID",
|
|
"getComputerID",
|
|
"setComputerLabel",
|
|
"computerLabel",
|
|
"getComputerLabel",
|
|
"clock",
|
|
"time",
|
|
"day",
|
|
"cancelTimer",
|
|
"cancelAlarm",
|
|
"epoch",
|
|
"date",
|
|
};
|
|
}
|
|
|
|
private static float getTimeForCalendar( Calendar c )
|
|
{
|
|
float time = c.get( Calendar.HOUR_OF_DAY );
|
|
time += c.get( Calendar.MINUTE ) / 60.0f;
|
|
time += c.get( Calendar.SECOND ) / (60.0f * 60.0f);
|
|
return time;
|
|
}
|
|
|
|
private static int getDayForCalendar( Calendar c )
|
|
{
|
|
GregorianCalendar g = c instanceof GregorianCalendar ? (GregorianCalendar) c : new GregorianCalendar();
|
|
int year = c.get( Calendar.YEAR );
|
|
int day = 0;
|
|
for( int y = 1970; y < year; y++ )
|
|
{
|
|
day += g.isLeapYear( y ) ? 366 : 365;
|
|
}
|
|
day += c.get( Calendar.DAY_OF_YEAR );
|
|
return day;
|
|
}
|
|
|
|
private static long getEpochForCalendar( Calendar c )
|
|
{
|
|
return c.getTime().getTime();
|
|
}
|
|
|
|
@Override
|
|
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
|
|
{
|
|
switch( method )
|
|
{
|
|
case 0: // queueEvent
|
|
queueLuaEvent( getString( args, 0 ), trimArray( args, 1 ) );
|
|
return null;
|
|
case 1:
|
|
{
|
|
// startTimer
|
|
double timer = getReal( args, 0 );
|
|
synchronized( m_timers )
|
|
{
|
|
m_timers.put( m_nextTimerToken, new Timer( (int) Math.round( timer / 0.05 ) ) );
|
|
return new Object[] { m_nextTimerToken++ };
|
|
}
|
|
}
|
|
case 2:
|
|
{
|
|
// setAlarm
|
|
double time = getReal( args, 0 );
|
|
if( time < 0.0 || time >= 24.0 )
|
|
{
|
|
throw new LuaException( "Number out of range" );
|
|
}
|
|
synchronized( m_alarms )
|
|
{
|
|
int day = time > m_time ? m_day : m_day + 1;
|
|
m_alarms.put( m_nextAlarmToken, new Alarm( time, day ) );
|
|
return new Object[] { m_nextAlarmToken++ };
|
|
}
|
|
}
|
|
case 3: // shutdown
|
|
m_apiEnvironment.shutdown();
|
|
return null;
|
|
case 4: // reboot
|
|
m_apiEnvironment.reboot();
|
|
return null;
|
|
case 5:
|
|
case 6: // computerID/getComputerID
|
|
return new Object[] { getComputerID() };
|
|
case 7:
|
|
{
|
|
// setComputerLabel
|
|
String label = optString( args, 0, null );
|
|
m_apiEnvironment.setLabel( StringUtil.normaliseLabel( label ) );
|
|
return null;
|
|
}
|
|
case 8:
|
|
case 9:
|
|
{
|
|
// computerLabel/getComputerLabel
|
|
String label = m_apiEnvironment.getLabel();
|
|
if( label != null )
|
|
{
|
|
return new Object[] { label };
|
|
}
|
|
return null;
|
|
}
|
|
case 10: // clock
|
|
synchronized( m_timers )
|
|
{
|
|
return new Object[] { m_clock * 0.05 };
|
|
}
|
|
case 11:
|
|
{
|
|
// time
|
|
Object value = args.length > 0 ? args[0] : null;
|
|
if( value instanceof Map ) return new Object[] { LuaDateTime.fromTable( (Map<?, ?>) value ) };
|
|
|
|
String param = optString( args, 0, "ingame" );
|
|
switch( param.toLowerCase( Locale.ROOT ) )
|
|
{
|
|
case "utc":
|
|
{
|
|
// Get Hour of day (UTC)
|
|
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
|
|
return new Object[] { getTimeForCalendar( c ) };
|
|
}
|
|
case "local":
|
|
{
|
|
// Get Hour of day (local time)
|
|
Calendar c = Calendar.getInstance();
|
|
return new Object[] { getTimeForCalendar( c ) };
|
|
}
|
|
case "ingame":
|
|
// Get ingame hour
|
|
synchronized( m_alarms )
|
|
{
|
|
return new Object[] { m_time };
|
|
}
|
|
default:
|
|
throw new LuaException( "Unsupported operation" );
|
|
}
|
|
}
|
|
case 12:
|
|
{
|
|
// day
|
|
String param = optString( args, 0, "ingame" );
|
|
switch( param.toLowerCase( Locale.ROOT ) )
|
|
{
|
|
case "utc":
|
|
{
|
|
// Get numbers of days since 1970-01-01 (utc)
|
|
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
|
|
return new Object[] { getDayForCalendar( c ) };
|
|
}
|
|
case "local":
|
|
{
|
|
// Get numbers of days since 1970-01-01 (local time)
|
|
Calendar c = Calendar.getInstance();
|
|
return new Object[] { getDayForCalendar( c ) };
|
|
}
|
|
case "ingame":
|
|
// Get game day
|
|
synchronized( m_alarms )
|
|
{
|
|
return new Object[] { m_day };
|
|
}
|
|
default:
|
|
throw new LuaException( "Unsupported operation" );
|
|
}
|
|
}
|
|
case 13:
|
|
{
|
|
// cancelTimer
|
|
int token = getInt( args, 0 );
|
|
synchronized( m_timers )
|
|
{
|
|
m_timers.remove( token );
|
|
}
|
|
return null;
|
|
}
|
|
case 14:
|
|
{
|
|
// cancelAlarm
|
|
int token = getInt( args, 0 );
|
|
synchronized( m_alarms )
|
|
{
|
|
m_alarms.remove( token );
|
|
}
|
|
return null;
|
|
}
|
|
case 15: // epoch
|
|
{
|
|
String param = optString( args, 0, "ingame" );
|
|
switch( param.toLowerCase( Locale.ROOT ) )
|
|
{
|
|
case "utc":
|
|
{
|
|
// Get utc epoch
|
|
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
|
|
return new Object[] { getEpochForCalendar( c ) };
|
|
}
|
|
case "local":
|
|
{
|
|
// Get local epoch
|
|
Calendar c = Calendar.getInstance();
|
|
return new Object[] { getEpochForCalendar( c ) };
|
|
}
|
|
case "ingame":
|
|
// Get in-game epoch
|
|
synchronized( m_alarms )
|
|
{
|
|
return new Object[] { m_day * 86400000 + (int) (m_time * 3600000.0f) };
|
|
}
|
|
default:
|
|
throw new LuaException( "Unsupported operation" );
|
|
}
|
|
}
|
|
case 16: // date
|
|
{
|
|
String format = optString( args, 0, "%c" );
|
|
long time = optLong( args, 1, Instant.now().getEpochSecond() );
|
|
|
|
Instant instant = Instant.ofEpochSecond( time );
|
|
ZonedDateTime date;
|
|
ZoneOffset offset;
|
|
if( format.startsWith( "!" ) )
|
|
{
|
|
offset = ZoneOffset.UTC;
|
|
date = ZonedDateTime.ofInstant( instant, offset );
|
|
format = format.substring( 1 );
|
|
}
|
|
else
|
|
{
|
|
ZoneId id = ZoneId.systemDefault();
|
|
offset = id.getRules().getOffset( instant );
|
|
date = ZonedDateTime.ofInstant( instant, id );
|
|
}
|
|
|
|
if( format.equals( "*t" ) ) return new Object[] { LuaDateTime.toTable( date, offset, instant ) };
|
|
|
|
DateTimeFormatterBuilder formatter = new DateTimeFormatterBuilder();
|
|
LuaDateTime.format( formatter, format, offset );
|
|
return new Object[] { formatter.toFormatter( Locale.ROOT ).format( date ) };
|
|
}
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Private methods
|
|
|
|
private void queueLuaEvent( String event, Object[] args )
|
|
{
|
|
m_apiEnvironment.queueEvent( event, args );
|
|
}
|
|
|
|
private Object[] trimArray( Object[] array, int skip )
|
|
{
|
|
return Arrays.copyOfRange( array, skip, array.length );
|
|
}
|
|
|
|
private int getComputerID()
|
|
{
|
|
return m_apiEnvironment.getComputerID();
|
|
}
|
|
}
|