1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-11-16 23:04:53 +00:00

Add some tests for the new executor system

And fix a couple of bugs picked up by said tests
This commit is contained in:
SquidDev 2018-09-18 18:21:00 +01:00
parent 4b741739e8
commit 8819f2559d
11 changed files with 558 additions and 30 deletions

View File

@ -197,3 +197,9 @@ gradle.projectsEvaluated {
runClient.outputs.upToDateWhen { false }
runServer.outputs.upToDateWhen { false }
test {
testLogging {
events "failed", "standardOut", "standardError"
}
}

View File

@ -44,10 +44,12 @@ public interface ILuaObject
* for the possible values and conversion rules.
* @return An array of objects, representing the values you wish to return to the Lua program. See
* {@link MethodResult#of(Object...)} for the valid values and conversion rules.
* @throws LuaException If the task could not be queued, or if the task threw an exception.
* @throws LuaException If you throw any exception from this function, a lua error will be raised with the
* same message as your exception. Use this to throw appropriate errors if the wrong
* arguments are supplied to your method.
* @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
* InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.w
* intercepted, or the computer will leak memory and end up in a broken state.
* @see IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])
* @deprecated Use {@link #callMethod(ICallContext, int, Object[])} instead.
*/
@ -68,12 +70,14 @@ public interface ILuaObject
* for the possible values and conversion rules.
* @return The result of calling this method. Use {@link MethodResult#empty()} to return nothing or
* {@link MethodResult#of(Object...)} to return several values.
* @throws LuaException If the task could not be queued, or if the task threw an exception.
* @throws LuaException If you throw any exception from this function, a lua error will be raised with the
* same message as your exception. Use this to throw appropriate errors if the wrong
* arguments are supplied to your method.
* @see IPeripheral#callMethod(IComputerAccess, ICallContext, int, Object[])
* @see MethodResult
*/
@Nonnull
@SuppressWarnings({ "deprecation" })
@SuppressWarnings( { "deprecation" } )
default MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{
return MethodResult.withLuaContext( lua -> callMethod( lua, method, arguments ) );

View File

@ -89,16 +89,17 @@ public abstract class MethodResult
* Normally you'll wish to consume the event using {@link #then(ILuaFunction)}. This can be done slightly more
* easily with {@link #pullEvent(String, ILuaFunction)}.
*
* @param filter The event name to filter on.
* @return The constructed method result. This evaluates to the name of the event that occurred, and any event
* parameters.
* @see #pullEvent(String, ILuaFunction)
* @see #pullEvent()
*/
@Nonnull
public static MethodResult pullEvent( @Nonnull String event )
public static MethodResult pullEvent( @Nonnull String filter )
{
Preconditions.checkNotNull( event, "event cannot be null" );
return new OnEvent( false, event );
Preconditions.checkNotNull( filter, "event cannot be null" );
return new OnEvent( false, filter );
}
/**
@ -108,6 +109,7 @@ public abstract class MethodResult
* If you want to listen to a specific event, it's easier to use {@link #pullEvent(String, ILuaFunction)} rather
* than running until the desired event is found.
*
* @param callback The function to call when the event is received.
* @return The constructed method result. This evaluates to the result of the {@code callback}.
* @see #pullEvent()
* @see #pullEvent(String, ILuaFunction)
@ -123,6 +125,8 @@ public abstract class MethodResult
* Wait for the specified event to occur on the computer, suspending the coroutine until it arises. This method to
* {@link #pullEvent(String)} and {@link #then(ILuaFunction)}.
*
* @param filter The event name to filter on.
* @param callback The function to call when the event is received.
* @return The constructed method result. This evaluates to the result of the {@code callback}.
* @see #pullEvent(String)
* @see #pullEvent(ILuaFunction)
@ -151,19 +155,21 @@ public abstract class MethodResult
* The same as {@link #pullEvent(String)}, except {@code terminated} events are also passed to the callback, instead
* of throwing an error. Only use this if you want to prevent program termination, which is not recommended.
*
* @param filter The event name to filter on.
* @return The constructed method result. This evaluates to the name of the event that occurred, and any event
* parameters.
*/
@Nonnull
public static MethodResult pullEventRaw( @Nonnull String event )
public static MethodResult pullEventRaw( @Nonnull String filter )
{
return new OnEvent( true, event );
return new OnEvent( true, filter );
}
/**
* The same as {@link #pullEvent(ILuaFunction)}, except {@code terminated} events are also passed to the callback,
* instead of throwing an error. Only use this if you want to prevent program termination, which is not recommended.
*
* @param callback The function to call when the event is received.
* @return The constructed method result. This evaluates to the result of the {@code callback}.
*/
@Nonnull
@ -178,6 +184,8 @@ public abstract class MethodResult
* callback, instead of throwing an error. Only use this if you want to prevent program termination, which is not
* recommended.
*
* @param filter The event name to filter on.
* @param callback The function to call when the event is received.
* @return The constructed method result. This evaluates to the result of the {@code callback}.
*/
@Nonnull
@ -237,6 +245,8 @@ public abstract class MethodResult
*
* @param context The context to execute with.
* @return The resulting values.
* @throws LuaException If an error was thrown while executing one of the methods within this future.
* @throws InterruptedException If the user shuts down or reboots the computer while the coroutine is suspended.
* @see #withLuaContext(ILuaContextTask)
* @deprecated This should not be used except to interface between the two call systems.
*/

View File

@ -157,11 +157,14 @@ class CobaltLuaContext extends CobaltCallContext implements ILuaContext
} );
}
Object[] resume( LuaState state, CobaltLuaMachine machine, Object[] args ) throws LuaError, UnwindThrowable
void resume( Object[] args )
{
values = args;
resume.signal();
}
Object[] await( LuaState state, CobaltLuaMachine machine ) throws LuaError, UnwindThrowable
{
if( !done )
{
try
@ -170,14 +173,12 @@ class CobaltLuaContext extends CobaltCallContext implements ILuaContext
}
catch( InterruptedException e )
{
state.debug.onReturn();
throw new LuaError( "Java Exception Thrown: " + e.toString(), 0 );
}
}
if( done )
{
state.debug.onReturn();
if( exception != null ) throw exception;
return values;
}

View File

@ -235,6 +235,7 @@ public class CobaltLuaMachine implements ILuaMachine
}
catch( LuaError e )
{
if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Main thread crashed", e );
m_mainRoutine.abandon();
m_mainRoutine = null;
}

View File

@ -13,8 +13,6 @@ import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.core.computer.Computer;
import org.squiddev.cobalt.*;
import org.squiddev.cobalt.debug.DebugFrame;
import org.squiddev.cobalt.debug.DebugHandler;
import org.squiddev.cobalt.debug.DebugState;
import org.squiddev.cobalt.function.VarArgFunction;
@ -62,12 +60,19 @@ class CobaltWrapperFunction extends VarArgFunction implements Resumable<CobaltWr
throw new LuaError( "Java Exception Thrown: " + e.toString(), 0 );
}
// Verify we've a "well formed" future
if( future == null )
{
ComputerCraft.log.error( "Null result from " + delegate );
throw new LuaError( "Java Exception Thrown: Null result" );
}
// Fast path for immediate results
if( future instanceof MethodResult.Immediate )
{
return machine.toValues( ((MethodResult.Immediate) future).getResult() );
}
State context = new State();
try
{
@ -75,12 +80,11 @@ class CobaltWrapperFunction extends VarArgFunction implements Resumable<CobaltWr
}
catch( UnwindThrowable e )
{
// Push our state onto the stack if need-be.
DebugHandler handler = state.debug;
DebugState ds = handler.getDebugState();
DebugFrame di = handler.onCall( ds, this );
di.state = context;
// Push our state onto the stack if need-be. Normally this wouldn't be safe and we
// should do this at the very beginning, but we know that we won't be calling anything
// else which will push to the resume stack.
DebugState ds = state.debug.getDebugState();
state.debug.onCall( ds, this, context );
throw e;
}
}
@ -88,11 +92,10 @@ class CobaltWrapperFunction extends VarArgFunction implements Resumable<CobaltWr
@Override
public Varargs resume( LuaState state, State context, Varargs args ) throws LuaError, UnwindThrowable
{
Varargs result;
try
{
Varargs result = doResume( state, context, args );
state.debug.onReturn();
return result;
result = doResume( state, context, args );
}
catch( LuaError e )
{
@ -104,6 +107,9 @@ class CobaltWrapperFunction extends VarArgFunction implements Resumable<CobaltWr
state.debug.onReturn();
throw new LuaError( e );
}
state.debug.onReturn();
return result;
}
private Varargs doResume( LuaState state, State context, Varargs args ) throws LuaError, UnwindThrowable
@ -126,7 +132,7 @@ class CobaltWrapperFunction extends VarArgFunction implements Resumable<CobaltWr
if( args.arg( 3 ).toBoolean() )
{
// Extract the return values from the event and return them
return runCallback( state, context, CobaltLuaMachine.toObjects( args, 4 ) );
return runFuture( state, context, context.taskResult );
}
else
{
@ -142,7 +148,8 @@ class CobaltWrapperFunction extends VarArgFunction implements Resumable<CobaltWr
}
else if( future instanceof MethodResult.WithLuaContext )
{
return runCallback( state, context, context.luaContext.resume( state, machine, CobaltLuaMachine.toObjects( args, 1 ) ) );
context.luaContext.resume( CobaltLuaMachine.toObjects( args, 1 ) );
return runCallback( state, context, context.luaContext.await( state, machine ) );
}
else
{
@ -223,6 +230,7 @@ class CobaltWrapperFunction extends VarArgFunction implements Resumable<CobaltWr
context.pending = future;
CobaltLuaContext luaContext = context.luaContext = new CobaltLuaContext( computer, state );
luaContext.execute( withContext.getConsumer() );
return runCallback( state, context, luaContext.await( state, machine ) );
}
else
{

View File

@ -0,0 +1,91 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.core.filesystem.FileMount;
import net.minecraftforge.fml.common.Loader;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
public class FakeComputerEnvironment implements IComputerEnvironment
{
private final boolean colour;
private final int id;
public FakeComputerEnvironment( int id, boolean colour )
{
this.colour = colour;
this.id = id;
}
@Override
public int getDay()
{
return 0;
}
@Override
public double getTimeOfDay()
{
return 0;
}
@Override
public boolean isColour()
{
return colour;
}
@Override
public int assignNewID()
{
return id;
}
@Override
public IWritableMount createSaveDirMount( String subPath, long capacity )
{
return new FileMount( new File( "computer/" + subPath ), capacity );
}
@Override
public IMount createResourceMount( String domain, String subPath )
{
String fullPath = "assets/" + domain + "/" + subPath;
URL url = ComputerCraft.class.getProtectionDomain().getCodeSource().getLocation();
File file = new File( url.getPath(), fullPath );
if( !file.exists() ) file = new File( "src/main/resources", fullPath );
if( !file.exists() ) throw new RuntimeException( "Cannot find ROM in " + file );
return new FileMount( file, 0 );
}
@Override
public InputStream createResourceFile( String domain, String subPath )
{
String fullPath = "assets/" + domain + "/" + subPath;
return ComputerCraft.class.getClassLoader().getResourceAsStream( fullPath );
}
@Override
public long getComputerSpaceLimit()
{
return ComputerCraft.computerSpaceLimit;
}
@Override
public String getHostString()
{
return "ComputerCraft ${version} (Minecraft " + Loader.MC_VERSION + ")";
}
}

View File

@ -0,0 +1,155 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.terminal.Terminal;
import org.apache.logging.log4j.LogManager;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.function.Consumer;
import static org.junit.Assert.fail;
public final class RunOnComputer
{
public static final int STARTUP_TIMEOUT = 10;
public static final int RUN_TIMEOUT = 100;
public static void run( String task ) throws Exception
{
run( task, x -> {
} );
}
public static void run( String task, Consumer<Computer> setup ) throws Exception
{
if( ComputerCraft.log == null ) ComputerCraft.log = LogManager.getLogger( "computercraft" );
ComputerCraft.logPeripheralErrors = true;
// Setup computer
Terminal terminal = new Terminal( ComputerCraft.terminalWidth_computer, ComputerCraft.terminalHeight_computer );
Computer computer = new Computer( new FakeComputerEnvironment( 0, true ), terminal, 0 );
// Register APIS
TestAPI api = new TestAPI( computer );
computer.addAPI( api );
setup.accept( computer );
// Setup the startup file
try( OutputStream stream = computer.getRootMount().openForWrite( "startup.lua" ) )
{
String program = "" +
"local function exec()\n" +
" " + task + "\n" +
"end\n" +
"test.finish(pcall(exec))\n";
stream.write( program.getBytes( StandardCharsets.UTF_8 ) );
}
// Turn on
ComputerThread.start();
computer.turnOn();
// Run until shutdown or we timeout
boolean everOn = false;
int ticks = 0;
do
{
computer.advance( 0.05 );
MainThread.executePendingTasks();
Thread.sleep( 50 );
ticks++;
everOn |= computer.isOn();
}
while( (computer.isOn() || (!everOn && ticks < STARTUP_TIMEOUT)) && ticks <= RUN_TIMEOUT );
// If we never finished (say, startup errored) then print the terminal. Otherwise throw the error
// where needed.
if( !api.finished )
{
int height = terminal.getHeight() - 1;
while( height >= 0 && terminal.getLine( height ).toString().trim().isEmpty() ) height--;
for( int y = 0; y <= height; y++ )
{
System.out.printf( "%2d | %s\n", y + 1, terminal.getLine( y ) );
}
fail( "Computer never finished" );
}
else if( api.error != null )
{
fail( api.error );
}
ComputerThread.stop();
}
private static class TestAPI implements ILuaAPI
{
private final Computer computer;
private boolean finished = false;
private String error;
private TestAPI( Computer computer )
{
this.computer = computer;
}
@Override
public String[] getNames()
{
return new String[]{ "test" };
}
@Nonnull
@Override
public String[] getMethodNames()
{
return new String[]{ "log", "finish" };
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
@Nonnull
@Override
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments )
{
switch( method )
{
case 0:
ComputerCraft.log.info( Objects.toString( arguments.length <= 0 ? null : arguments[0] ) );
return MethodResult.empty();
case 1:
{
if( arguments.length <= 0 || arguments[0] == null || arguments[0] == Boolean.FALSE )
{
error = Objects.toString( arguments.length <= 1 ? null : arguments[1] );
}
finished = true;
computer.shutdown();
return MethodResult.empty();
}
default:
return MethodResult.empty();
}
}
}
}

View File

@ -0,0 +1,243 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.lua;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.computer.RunOnComputer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;
@RunWith( Parameterized.class )
public class CobaltWrapperFunctionTest
{
@Parameterized.Parameter( 0 )
public String name;
@Parameterized.Parameter( 1 )
public String code;
@Parameterized.Parameters( name = "{0}" )
public static Collection<Object[]> parameters()
{
return Arrays.stream( new Object[][]{
new Object[]{ "empty", "assert(select('#', funcs.empty()) == 0)" },
new Object[]{ "identity", "assert(select('#', funcs.identity(1, 2, 3)) == 3)" },
new Object[]{ "pullEvent", "os.queueEvent('test') assert(funcs.pullEvent() == 'test')" },
new Object[]{ "pullEventTerminate", "os.queueEvent('terminate') assert(not pcall(funcs.pullEvent))" },
new Object[]{ "pullEventRaw", "os.queueEvent('test') assert(funcs.pullEventRaw() == 'test')" },
new Object[]{ "pullEventRawTerminate", "os.queueEvent('terminate') assert(funcs.pullEventRaw() == 'terminate')" },
new Object[]{ "mainThread", "assert(funcs.mainThread() == 1)" },
new Object[]{ "mainThreadMany", "for i = 1, 4 do assert(funcs.mainThread() == 1) end" }
} ).collect( Collectors.toList() );
}
/**
* Tests executing functions defined through the {@link MethodResult} API.
*/
@Test
public void testMethodResult() throws Exception
{
RunOnComputer.run( code, c -> c.addAPI( new MethodResultAPI() ) );
}
/**
* Tests executing functions defined through the {@link MethodResult} API called with the
* {@link ILuaContext} evaluator.
*/
@Test
public void testMethodResultEvaluate() throws Exception
{
RunOnComputer.run( code, c -> c.addAPI( new WrapperAPI( new MethodResultAPI() )
{
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
} ) );
}
/**
* Tests using {@link MethodResult#then(ILuaFunction)} afterwards
*/
@Test
public void testMethodResultThen() throws Exception
{
RunOnComputer.run( code, c -> c.addAPI( new WrapperAPI( new MethodResultAPI() ) {
@Nonnull
@Override
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{
return super.callMethod( context, method, arguments )
.then( x -> MethodResult.onMainThread( () -> MethodResult.of( x ).then( MethodResult::of ) ) )
.then( MethodResult::of );
}
} ) );
}
/**
* Tests executing functions defined through the {@link ILuaContext} API.
*/
@Test
public void testLuaContext() throws Exception
{
RunOnComputer.run( code, c -> c.addAPI( new LuaContextAPI() ) );
}
/**
* Tests executing functions defined through the {@link ILuaContext} API called with the
* {@link MethodResult} evaluator.
*/
@Test
public void testWithLuaContext() throws Exception
{
RunOnComputer.run( code, c -> c.addAPI( new WrapperAPI( new LuaContextAPI() )
{
@Nonnull
@Override
@SuppressWarnings( "deprecation" )
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{
return MethodResult.withLuaContext( c -> callMethod( c, method, arguments ) );
}
} ) );
}
private static class MethodResultAPI implements ILuaAPI
{
@Override
public String[] getNames()
{
return new String[]{ "funcs" };
}
@Nonnull
@Override
public String[] getMethodNames()
{
return new String[]{ "empty", "identity", "pullEvent", "pullEventRaw", "mainThread" };
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
@Nonnull
@Override
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments )
{
switch( method )
{
case 0:
return MethodResult.empty();
case 1:
return MethodResult.of( arguments );
case 2:
return MethodResult.pullEvent( "test" );
case 3:
return MethodResult.pullEventRaw( "test" );
case 4:
return MethodResult.onMainThread( () -> MethodResult.of( 1 ) );
default:
return MethodResult.empty();
}
}
}
private static class LuaContextAPI implements ILuaAPI
{
@Override
public String[] getNames()
{
return new String[]{ "funcs" };
}
@Nonnull
@Override
public String[] getMethodNames()
{
return new String[]{ "empty", "identity", "pullEvent", "pullEventRaw", "mainThread" };
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
switch( method )
{
case 0:
return null;
case 1:
return arguments;
case 2:
return context.pullEvent( "test" );
case 3:
return context.pullEventRaw( "test" );
case 4:
return context.executeMainThreadTask( () -> new Object[]{ 1 } );
default:
return null;
}
}
}
public static class WrapperAPI implements ILuaAPI
{
private final ILuaAPI api;
public WrapperAPI( ILuaAPI api )
{
this.api = api;
}
@Override
public String[] getNames()
{
return api.getNames();
}
@Nonnull
@Override
public String[] getMethodNames()
{
return api.getMethodNames();
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return api.callMethod( context, method, arguments );
}
@Nonnull
@Override
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{
return api.callMethod( context, method, arguments );
}
}
}

View File

@ -4,8 +4,9 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNetwork;
import dan200.computercraft.api.network.wired.IWiredNetworkChange;
@ -249,7 +250,7 @@ public class NetworkTest
}
@Test
@Ignore("Takes a long time to run, mostly for stress testing")
@Ignore( "Takes a long time to run, mostly for stress testing" )
public void testLarge()
{
final int BRUTE_SIZE = 16;
@ -410,11 +411,19 @@ public class NetworkTest
@Nullable
@Override
public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
@Deprecated
public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments )
{
return new Object[0];
}
@Nonnull
@Override
public MethodResult callMethod( @Nonnull IComputerAccess computer, @Nonnull ICallContext context, int method, @Nonnull Object[] arguments )
{
return MethodResult.empty();
}
@Override
public boolean equals( @Nullable IPeripheral other )
{
@ -427,7 +436,7 @@ public class NetworkTest
private final int size;
private final T[] box;
@SuppressWarnings("unchecked")
@SuppressWarnings( "unchecked" )
public Grid( int size )
{
this.size = size;