Initial unified commit

This commit is contained in:
Tefek 2020-08-18 17:36:08 +02:00
commit 5ffbee26f7
191 changed files with 10910 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/*/target/
/*/.settings
/*/.project
/*/.classpath
/.project

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 493msi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,22 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cz.tefek</groupId>
<artifactId>plutocommandparser</artifactId>
<version>0.1</version>
<name>plutocommandparser</name>
<description>Pluto Command Parser</description>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.release>11</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>cz.tefek</groupId>
<artifactId>plutoio2</artifactId>
<version>0.2</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,18 @@
package cz.tefek.pluto.command;
public abstract class CommandBase
{
public abstract String name();
public abstract String[] aliases();
public abstract String description();
public abstract Class<?> commandClass();
@Override
public final int hashCode()
{
return this.name().hashCode();
}
}

View File

@ -0,0 +1,96 @@
package cz.tefek.pluto.command.context;
import cz.tefek.pluto.command.CommandBase;
public class CommandContextBuilder
{
private CommandContext ctx;
public CommandContextBuilder()
{
this.ctx = new CommandContext();
}
public CommandContextBuilder prefix(String prefix)
{
this.ctx.usedPrefix = prefix;
return this;
}
public CommandContextBuilder alias(String alias)
{
this.ctx.usedAlias = alias;
return this;
}
public CommandContextBuilder command(CommandBase command)
{
this.ctx.command = command;
return this;
}
public CommandContext resolved()
{
this.ctx.resolved = true;
return this.ctx;
}
public CommandContext unresolved(EnumCommandParseFailure cause)
{
this.ctx.resolved = false;
return this.ctx;
}
public static class CommandContext
{
private boolean resolved;
private EnumCommandParseFailure failureCause;
private String usedPrefix;
private String usedAlias;
private CommandBase command;
private CommandContext()
{
}
public String getUsedPrefix()
{
return this.usedPrefix;
}
public String getUsedAlias()
{
return this.usedAlias;
}
public CommandBase getCommand()
{
return this.command;
}
public boolean isResolved()
{
return this.resolved;
}
public EnumCommandParseFailure getFailureCause()
{
return this.failureCause;
}
}
public enum EnumCommandParseFailure
{
UNRESOLVED_PREFIX,
UNRESOLVED_COMMAND_NAME,
UNRESOLVED_PARAMETERS,
UNRESOLVED_UNEXPECTED_STATE;
}
}

View File

@ -0,0 +1,252 @@
package cz.tefek.pluto.command.parser;
import java.util.PrimitiveIterator.OfInt;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import cz.tefek.pluto.command.CommandBase;
import cz.tefek.pluto.command.context.CommandContextBuilder;
import cz.tefek.pluto.command.context.CommandContextBuilder.CommandContext;
import cz.tefek.pluto.command.context.CommandContextBuilder.EnumCommandParseFailure;
import cz.tefek.pluto.command.registry.CommandRegistry;
public class CommandParser
{
private String text;
private Set<OfInt> prefixes;
private EnumParserState state;
private StringBuilder prefixBuilder;
private StringBuilder commandNameBuilder;
private CommandBase command;
private StringBuilder parameterBuilder;
private CommandContextBuilder ctx;
private static final int CP_QUOTE = '"';
public CommandParser(String text)
{
this.text = text;
this.state = EnumParserState.BEGIN;
}
private boolean readCodepoint(int cp)
{
switch (this.state)
{
case READING_PREFIX:
this.prefixBuilder.appendCodePoint(cp);
this.prefixes.removeIf(ii -> ii.nextInt() != cp);
if (this.prefixes.isEmpty())
{
this.state = EnumParserState.END_NO_PREFIX;
return false;
}
if (this.hasEmptyPrefix())
{
this.ctx.prefix(this.prefixBuilder.toString());
this.state = EnumParserState.READING_COMMAND;
}
break;
case READING_COMMAND:
this.commandNameBuilder.appendCodePoint(cp);
if (Character.isWhitespace(cp))
{
if (!this.resolveCommand())
{
return false;
}
this.state = EnumParserState.READ_WHITESPACE;
}
break;
case READ_WHITESPACE:
if (Character.isWhitespace(cp))
{
break;
}
if (cp == CP_QUOTE)
{
this.state = EnumParserState.READING_PARAMETER_QUOTED;
}
else
{
this.parameterBuilder.appendCodePoint(cp);
this.state = EnumParserState.READING_PARAMETER;
}
break;
case READING_PARAMETER_QUOTED:
if (cp == CP_QUOTE)
{
this.state = EnumParserState.READING_PARAMETER_CANDIDATE_UNQUOTE;
}
else
{
this.parameterBuilder.appendCodePoint(cp);
}
break;
case READING_PARAMETER:
if (Character.isWhitespace(cp))
{
this.emitParameter();
this.state = EnumParserState.READ_WHITESPACE;
}
else
{
this.parameterBuilder.appendCodePoint(cp);
}
break;
case READING_PARAMETER_CANDIDATE_UNQUOTE:
if (Character.isWhitespace(cp))
{
this.emitParameter();
this.state = EnumParserState.READ_WHITESPACE;
}
else
{
this.parameterBuilder.appendCodePoint(cp);
this.state = EnumParserState.READING_PARAMETER_QUOTED;
}
break;
case END:
this.state = EnumParserState.UNEXPECTED_STATE_FALLBACK;
return false;
default:
this.state = EnumParserState.UNEXPECTED_STATE_FALLBACK;
return false;
}
return true;
}
private boolean resolveCommand()
{
var alias = this.commandNameBuilder.toString();
this.ctx.alias(alias);
this.command = CommandRegistry.getByAlias(alias);
if (this.command == null)
{
this.state = EnumParserState.END_NO_COMMAND;
return false;
}
return true;
}
private void emitParameter()
{
}
private boolean hasEmptyPrefix()
{
return this.prefixes.stream().filter(Predicate.not(OfInt::hasNext)).findAny().isPresent();
}
/**
* Parse using this parser and supplied prefixes. This function also
* resolves the command context and the parameters. Yeah it does a lot of
* stuff.
*
*/
public CommandContext parse(Set<String> prefixes)
{
if (this.state != EnumParserState.BEGIN)
{
throw new IllegalStateException("Cannot run a parser that is not in the BEGIN state.");
}
this.prefixBuilder = new StringBuilder();
this.ctx = new CommandContextBuilder();
this.prefixes = prefixes.stream().map(String::codePoints).map(IntStream::iterator).collect(Collectors.toSet());
if (prefixes.isEmpty() || this.hasEmptyPrefix())
{
this.state = EnumParserState.READING_COMMAND;
}
else
{
this.state = EnumParserState.READING_PREFIX;
}
this.text.codePoints().takeWhile(this::readCodepoint);
// Update the state for EOF
switch (this.state)
{
case READING_PARAMETER_QUOTED:
case READ_WHITESPACE:
case READING_PARAMETER:
case READING_PARAMETER_CANDIDATE_UNQUOTE:
this.state = EnumParserState.END;
this.emitParameter();
break;
case READING_COMMAND:
if (this.resolveCommand())
{
this.state = EnumParserState.END;
}
break;
default:
break;
}
// Check the end state
switch (this.state)
{
case READING_PREFIX:
case END_NO_PREFIX:
return this.ctx.unresolved(EnumCommandParseFailure.UNRESOLVED_PREFIX);
case END_NO_COMMAND:
return this.ctx.unresolved(EnumCommandParseFailure.UNRESOLVED_COMMAND_NAME);
case END:
break;
default:
return this.ctx.unresolved(EnumCommandParseFailure.UNRESOLVED_UNEXPECTED_STATE);
}
// At this point we are 100% sure the command was resolved and can validate the parameters
/**
*
* TODO: Validate parameters here
*
*/
return this.ctx.resolved();
}
}

View File

@ -0,0 +1,17 @@
package cz.tefek.pluto.command.parser;
public enum EnumParserState
{
BEGIN,
READING_PREFIX,
READING_COMMAND,
READING_PARAMETER,
READING_PARAMETER_QUOTED,
READING_PARAMETER_CANDIDATE_UNQUOTE,
READ_WHITESPACE,
END_NO_PREFIX,
END_NO_COMMAND,
END_EMISSION_FAILURE,
END,
UNEXPECTED_STATE_FALLBACK;
}

View File

@ -0,0 +1,30 @@
package cz.tefek.pluto.command.platform;
import cz.tefek.pluto.command.context.CommandContextBuilder.EnumCommandParseFailure;
public abstract class CommandPlatform
{
public abstract String getID();
public abstract String getName();
public boolean shouldWarnOn(EnumCommandParseFailure failure)
{
switch (failure)
{
case UNRESOLVED_PREFIX:
return false;
case UNRESOLVED_COMMAND_NAME:
return false;
case UNRESOLVED_UNEXPECTED_STATE:
return false;
default:
return true;
}
}
public abstract int getMessageLimit();
}

View File

@ -0,0 +1,69 @@
package cz.tefek.pluto.command.registry;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import cz.tefek.io.pluto.debug.Logger;
import cz.tefek.io.pluto.debug.SmartSeverity;
import cz.tefek.pluto.command.CommandBase;
public final class CommandRegistry
{
private static CommandRegistry instance;
private Set<CommandBase> commands;
private Map<String, CommandBase> aliasTable;
static
{
instance = new CommandRegistry();
}
private CommandRegistry()
{
this.aliasTable = new HashMap<>();
this.commands = new HashSet<>();
}
private void registerAlias(String alias, CommandBase command)
{
if (this.aliasTable.containsKey(alias))
{
Logger.logf(SmartSeverity.ERROR, "Alias '%s' for command '%s' is already used, skipping.\n", alias, command.name());
return;
}
}
public static void registerCommand(CommandBase command)
{
if (!instance.commands.add(command))
{
Logger.logf(SmartSeverity.ERROR, "Command '%s' is already registered, skipping.\n", command.name());
return;
}
instance.registerAlias(command.name(), command);
Arrays.stream(command.aliases()).forEach(alias -> instance.registerAlias(alias, command));
}
public static CommandBase getByAlias(String alias)
{
return instance.aliasTable.get(alias);
}
public static Set<CommandBase> getCommands()
{
return Collections.unmodifiableSet(instance.commands);
}
public static void clear()
{
instance = new CommandRegistry();
}
}

View File

@ -0,0 +1,24 @@
package cz.tefek.pluto.command.resolver;
public abstract class AbstractResolveException extends RuntimeException
{
/**
*
*/
private static final long serialVersionUID = -8934505669078637864L;
public AbstractResolveException(Exception exception)
{
super(exception);
}
public AbstractResolveException(String what)
{
super(what);
}
protected AbstractResolveException()
{
}
}

View File

@ -0,0 +1,6 @@
package cz.tefek.pluto.command.resolver;
public abstract class AbstractResolver
{
public abstract Class<?> getOutputType();
}

View File

@ -0,0 +1,13 @@
package cz.tefek.pluto.command.resolver;
public enum EnumResolveFailure
{
INT_PARSE,
LONG_PARSE,
FLOAT_PARSE,
DOUBLE_PARSE,
FRAC_INVALID_PERCENTAGE,
FRAC_PERCENTAGE_OUT_OF_RANGE,
FRAC_VALUE_HIGHER_THAN_BASE,
OTHER;
}

View File

@ -0,0 +1,21 @@
package cz.tefek.pluto.command.resolver;
import java.util.function.Function;
public abstract class GenericResolver<R> extends AbstractResolver
{
protected Function<String, R> func;
public GenericResolver(Function<String, R> func)
{
this.func = func;
}
public R apply(String param)
{
return this.func.apply(param);
}
@Override
public abstract Class<R> getOutputType();
}

View File

@ -0,0 +1,22 @@
package cz.tefek.pluto.command.resolver;
public class ResolveException extends AbstractResolveException
{
/**
*
*/
private static final long serialVersionUID = -3098373878754649161L;
private EnumResolveFailure failure;
public ResolveException(EnumResolveFailure failure)
{
this.failure = failure;
}
public EnumResolveFailure getResolveFailure()
{
return this.failure;
}
}

View File

@ -0,0 +1,23 @@
package cz.tefek.pluto.command.resolver.primitive;
import cz.tefek.pluto.command.resolver.EnumResolveFailure;
import cz.tefek.pluto.command.resolver.ResolveException;
public class BasicDoubleResolver extends DoubleResolver
{
public BasicDoubleResolver()
{
super(str ->
{
try
{
return Double.parseDouble(str);
}
catch (NumberFormatException e)
{
throw new ResolveException(EnumResolveFailure.DOUBLE_PARSE);
}
});
}
}

View File

@ -0,0 +1,22 @@
package cz.tefek.pluto.command.resolver.primitive;
import cz.tefek.pluto.command.resolver.EnumResolveFailure;
import cz.tefek.pluto.command.resolver.ResolveException;
public class BasicIntResolver extends IntResolver
{
public BasicIntResolver()
{
super(str ->
{
try
{
return Integer.parseInt(str);
}
catch (NumberFormatException e)
{
throw new ResolveException(EnumResolveFailure.INT_PARSE);
}
});
}
}

View File

@ -0,0 +1,23 @@
package cz.tefek.pluto.command.resolver.primitive;
import cz.tefek.pluto.command.resolver.EnumResolveFailure;
import cz.tefek.pluto.command.resolver.ResolveException;
public class BasicLongResolver extends LongResolver
{
public BasicLongResolver()
{
super(str ->
{
try
{
return Long.parseLong(str);
}
catch (NumberFormatException e)
{
throw new ResolveException(EnumResolveFailure.LONG_PARSE);
}
});
}
}

View File

@ -0,0 +1,26 @@
package cz.tefek.pluto.command.resolver.primitive;
import java.util.function.ToDoubleFunction;
import cz.tefek.pluto.command.resolver.AbstractResolver;
public class DoubleResolver extends AbstractResolver
{
protected ToDoubleFunction<String> func;
public DoubleResolver(ToDoubleFunction<String> func)
{
this.func = func;
}
public double apply(String param)
{
return this.func.applyAsDouble(param);
}
@Override
public Class<?> getOutputType()
{
return double.class;
}
}

View File

@ -0,0 +1,63 @@
package cz.tefek.pluto.command.resolver.primitive;
import cz.tefek.pluto.command.resolver.EnumResolveFailure;
import cz.tefek.pluto.command.resolver.ResolveException;
public class IntFractionResolver extends IntResolver
{
public IntFractionResolver(int base)
{
super(str -> parseAmount(str, base));
}
private static int parseAmount(String amountString, int base) throws ResolveException
{
if (amountString.equalsIgnoreCase("all") || amountString.equalsIgnoreCase("everything"))
{
return base;
}
if (amountString.equalsIgnoreCase("half"))
{
return base / 2;
}
if (amountString.endsWith("%"))
{
try
{
float percentage = Float.parseFloat(amountString.substring(0, amountString.length() - 1));
if (percentage < 0 || percentage > 100)
{
throw new ResolveException(EnumResolveFailure.FRAC_PERCENTAGE_OUT_OF_RANGE);
}
else
{
return Math.round(percentage / 100.0f * base);
}
}
catch (NumberFormatException e1)
{
throw new ResolveException(EnumResolveFailure.FRAC_INVALID_PERCENTAGE);
}
}
try
{
int amount = Integer.parseInt(amountString);
if (amount > base)
{
throw new ResolveException(EnumResolveFailure.FRAC_VALUE_HIGHER_THAN_BASE);
}
return amount;
}
catch (NumberFormatException e)
{
throw new ResolveException(EnumResolveFailure.INT_PARSE);
}
}
}

View File

@ -0,0 +1,26 @@
package cz.tefek.pluto.command.resolver.primitive;
import java.util.function.ToIntFunction;
import cz.tefek.pluto.command.resolver.AbstractResolver;
public class IntResolver extends AbstractResolver
{
protected ToIntFunction<String> func;
public IntResolver(ToIntFunction<String> func)
{
this.func = func;
}
public int apply(String param)
{
return this.func.applyAsInt(param);
}
@Override
public final Class<?> getOutputType()
{
return int.class;
}
}

View File

@ -0,0 +1,26 @@
package cz.tefek.pluto.command.resolver.primitive;
import java.util.function.ToLongFunction;
import cz.tefek.pluto.command.resolver.AbstractResolver;
public class LongResolver extends AbstractResolver
{
protected ToLongFunction<String> func;
public LongResolver(ToLongFunction<String> func)
{
this.func = func;
}
public long apply(String param)
{
return this.func.applyAsLong(param);
}
@Override
public final Class<?> getOutputType()
{
return long.class;
}
}

View File

@ -0,0 +1,41 @@
package cz.tefek.pluto.command.resolver;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import cz.tefek.pluto.command.resolver.primitive.BasicIntResolver;
class BasicIntResolverTest
{
@Test
void parse()
{
var resolver = new BasicIntResolver();
Assertions.assertEquals(5, resolver.apply("5"));
Assertions.assertEquals(-50, resolver.apply("-50"));
Assertions.assertEquals(155, resolver.apply("155"));
Assertions.assertEquals(0, resolver.apply("0"));
Assertions.assertEquals(-0, resolver.apply("-0"));
}
@Test
void exceptions()
{
var resolver = new BasicIntResolver();
// No foreign characters
Assertions.assertThrows(NumberFormatException.class, () -> resolver.apply("abc"));
// No floats
Assertions.assertThrows(NumberFormatException.class, () -> resolver.apply("12.5"));
// No empty strings
Assertions.assertThrows(NumberFormatException.class, () -> resolver.apply(""));
}
}

1
plutodb/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/testdb

96
plutodb/pom.xml Normal file
View File

@ -0,0 +1,96 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cz.tefek</groupId>
<artifactId>plutodb</artifactId>
<version>0.1</version>
<name>plutodb</name>
<properties>
<maven.compiler.source>12</maven.compiler.source>
<maven.compiler.target>12</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<lwjgl.version>3.2.3</lwjgl.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-bom</artifactId>
<version>${lwjgl.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<profiles>
<profile>
<id>lwjgl-natives-linux-amd64</id>
<activation>
<os>
<family>unix</family>
<arch>amd64</arch>
</os>
</activation>
<properties>
<lwjgl.natives>natives-linux</lwjgl.natives>
</properties>
</profile>
<profile>
<id>lwjgl-natives-macos-amd64</id>
<activation>
<os>
<family>mac</family>
<arch>amd64</arch>
</os>
</activation>
<properties>
<lwjgl.natives>natives-macos</lwjgl.natives>
</properties>
</profile>
<profile>
<id>lwjgl-natives-windows-amd64</id>
<activation>
<os>
<family>windows</family>
<arch>amd64</arch>
</os>
</activation>
<properties>
<lwjgl.natives>natives-windows</lwjgl.natives>
</properties>
</profile>
<profile>
<id>lwjgl-natives-windows-x86</id>
<activation>
<os>
<family>windows</family>
<arch>x86</arch>
</os>
</activation>
<properties>
<lwjgl.natives>natives-windows-x86</lwjgl.natives>
</properties>
</profile>
</profiles>
<dependencies>
<dependency>
<groupId>cz.tefek</groupId>
<artifactId>plutoio2</artifactId>
<version>0.2</version>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-lmdb</artifactId>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl</artifactId>
<classifier>${lwjgl.natives}</classifier>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-lmdb</artifactId>
<classifier>${lwjgl.natives}</classifier>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,37 @@
package cz.tefek.plutodb;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
public interface ILMDBValueRecipe
{
public int sizeOf();
public void serialize(ByteBuffer output);
public void deserialize(ByteBuffer input);
public static int sizeOfUTF8(CharSequence string)
{
return Integer.BYTES + MemoryUtil.memLengthUTF8(string, false);
}
public static void putUTF8(CharSequence string, ByteBuffer output)
{
int strLen = MemoryUtil.memUTF8(string, false, output, output.position() + Integer.BYTES);
output.putInt(strLen);
output.position(output.position() + strLen);
}
public static String getUTF8(ByteBuffer input)
{
var strLen = input.getInt();
var string = MemoryUtil.memUTF8(input, strLen);
input.position(input.position() + strLen);
return string;
}
}

View File

@ -0,0 +1,78 @@
package cz.tefek.plutodb;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.util.lmdb.LMDB;
import org.lwjgl.util.lmdb.MDBVal;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public class LMDBDatabase<K extends LMDBKey, V extends ILMDBValueRecipe>
{
private LMDBTransaction transaction;
private int handle;
private MethodHandle valueConstructor;
LMDBDatabase(LMDBTransaction transaction, int handle, Class<V> valueClass) throws NoSuchMethodException, IllegalAccessException
{
this.handle = handle;
this.transaction = transaction;
this.valueConstructor = MethodHandles.lookup().findConstructor(valueClass, MethodType.methodType(void.class));
}
public long getHandle()
{
return this.handle;
}
public void put(K key, V value)
{
try (var stack = MemoryStack.stackPush())
{
var keyStruct = key.toMDBVal(stack);
var size = value.sizeOf();
var data = stack.malloc(size);
value.serialize(data);
data.flip();
var valueStruct = MDBVal.mallocStack(stack).mv_data(data).mv_size(size);
assert LMDB.mdb_put(this.transaction.getAddress(), this.handle, keyStruct, valueStruct, 0) == LMDB.MDB_SUCCESS;
}
}
public V get(K key)
{
try
{
var valueToUpdate = (V) this.valueConstructor.invoke();
return this.get(key, valueToUpdate);
}
catch (Throwable e)
{
e.printStackTrace();
return null;
}
}
public V get(K key, V valueToUpdate)
{
try (var stack = MemoryStack.stackPush())
{
var keyStruct = key.toMDBVal(stack);
var valueStruct = MDBVal.mallocStack(stack);
assert LMDB.mdb_get(this.transaction.getAddress(), this.handle, keyStruct, valueStruct) == LMDB.MDB_SUCCESS;
valueToUpdate.deserialize(valueStruct.mv_data());
return valueToUpdate;
}
}
}

View File

@ -0,0 +1,130 @@
package cz.tefek.plutodb;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.util.lmdb.LMDB;
import java.io.File;
import java.io.IOException;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
public class LMDBEnvironment implements AutoCloseable
{
private static final Set<File> envGuard = Collections.synchronizedSet(new HashSet<>());
private final File path;
private final long envPtr;
private final int maxDBs;
private static final int permissions = 0640;
public LMDBEnvironment(File path, int maxDBs, long mapSize) throws IOException
{
if (!envGuard.add(path))
{
throw new RuntimeException("Error: The LMDB Environment guard has detected a creation of an environment on an already locked file. To avoid synchronization issues, this cannot be permitted.");
}
this.path = path;
this.maxDBs = maxDBs;
try (var stack = MemoryStack.stackPush())
{
var ptrBuf = stack.mallocPointer(1);
int createCode = LMDB.mdb_env_create(ptrBuf);
if (createCode != LMDB.MDB_SUCCESS)
{
throw new RuntimeException(String.format("LMDB environment failed to create! Error code: %d", createCode));
}
this.envPtr = ptrBuf.get(0);
}
int mapSizeResult = LMDB.mdb_env_set_mapsize(this.envPtr, mapSize);
if (mapSizeResult != LMDB.MDB_SUCCESS)
{
throw new RuntimeException(String.format("Failed to set the MDB map size! Error code: %d", mapSizeResult));
}
int maxDBsResult = LMDB.mdb_env_set_maxdbs(this.envPtr, this.maxDBs);
if (maxDBsResult != LMDB.MDB_SUCCESS)
{
throw new RuntimeException(String.format("Failed to set the MDB maximum database count! Error code: %d", maxDBsResult));
}
if (!path.isDirectory())
{
path.mkdirs();
}
int openCode = LMDB.mdb_env_open(this.envPtr, path.getAbsolutePath(), 0, permissions);
if (openCode != LMDB.MDB_SUCCESS)
{
throw new RuntimeException(String.format("LMDB environment failed to open! Error code: %d", openCode));
}
}
public LMDBTransaction createTransaction()
{
return this.createTransaction(false);
}
public synchronized LMDBTransaction createTransaction(boolean readOnly)
{
try (var stack = MemoryStack.stackPush())
{
var ptrBuf = stack.mallocPointer(1);
int code = LMDB.mdb_txn_begin(this.envPtr, 0, readOnly ? LMDB.MDB_RDONLY : 0, ptrBuf);
if (code != LMDB.MDB_SUCCESS)
{
String errStr;
switch (code)
{
case LMDB.MDB_PANIC:
errStr = "MDB_PANIC";
break;
case LMDB.MDB_MAP_RESIZED:
errStr = "MDB_MAP_RESIZED";
break;
case LMDB.MDB_READERS_FULL:
errStr = "MDB_READERS_FULL";
break;
default:
throw new RuntimeException(String.format("Failed to create a transaction. Error code: %d", code));
}
throw new RuntimeException(String.format("Failed to create a transaction. Error code: %s", errStr));
}
long ptr = ptrBuf.get(0);
return new LMDBTransaction(this, ptr, readOnly);
}
}
public long getAddress()
{
return this.envPtr;
}
@Override
public void close() throws Exception
{
LMDB.mdb_env_close(this.envPtr);
envGuard.remove(this.path);
}
}

View File

@ -0,0 +1,29 @@
package cz.tefek.plutodb;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.util.lmdb.MDBVal;
public class LMDBIntegerKey extends LMDBKey
{
private int key;
private LMDBIntegerKey(int key)
{
this.key = key;
}
public static LMDBIntegerKey from(int rawKey)
{
return new LMDBIntegerKey(rawKey);
}
@Override
public MDBVal toMDBVal(MemoryStack stack)
{
var keyBuf = stack.malloc(Integer.BYTES);
keyBuf.putInt(this.key);
return MDBVal.mallocStack(stack).mv_size(keyBuf.capacity()).mv_data(keyBuf);
}
}

View File

@ -0,0 +1,9 @@
package cz.tefek.plutodb;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.util.lmdb.MDBVal;
public abstract class LMDBKey
{
public abstract MDBVal toMDBVal(MemoryStack stack);
}

View File

@ -0,0 +1,29 @@
package cz.tefek.plutodb;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.util.lmdb.MDBVal;
public class LMDBLongKey extends LMDBKey
{
private long key;
private LMDBLongKey(long key)
{
this.key = key;
}
public static LMDBLongKey from(long rawKey)
{
return new LMDBLongKey(rawKey);
}
@Override
public MDBVal toMDBVal(MemoryStack stack)
{
var keyBuf = stack.malloc(Long.BYTES);
keyBuf.putLong(this.key);
return MDBVal.mallocStack(stack).mv_size(keyBuf.capacity()).mv_data(keyBuf);
}
}

View File

@ -0,0 +1,23 @@
package cz.tefek.plutodb;
public class LMDBSchema<K extends LMDBKey, E extends ILMDBValueRecipe>
{
private String name;
private Class<E> valueType;
public LMDBSchema(String name, Class<E> valueType)
{
this.name = name;
this.valueType = valueType;
}
public String getName()
{
return this.name;
}
public Class<E> getValueRecipe()
{
return this.valueType;
}
}

View File

@ -0,0 +1,27 @@
package cz.tefek.plutodb;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.util.lmdb.MDBVal;
public class LMDBStringKey extends LMDBKey
{
private String key;
private LMDBStringKey(String key)
{
this.key = key;
}
public static LMDBStringKey from(String rawKey)
{
return new LMDBStringKey(rawKey);
}
@Override
public MDBVal toMDBVal(MemoryStack stack)
{
var keyBuf = stack.ASCII(this.key, false);
return MDBVal.mallocStack(stack).mv_size(keyBuf.capacity()).mv_data(keyBuf);
}
}

View File

@ -0,0 +1,108 @@
package cz.tefek.plutodb;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.util.lmdb.LMDB;
public class LMDBTransaction implements AutoCloseable
{
private long ptr;
private LMDBEnvironment env;
private boolean readOnly;
private boolean uncommited = true;
LMDBTransaction(LMDBEnvironment env, long ptr, boolean readOnly)
{
this.env = env;
this.ptr = ptr;
this.readOnly = readOnly;
}
public long getAddress()
{
return this.ptr;
}
public LMDBEnvironment getEnvironment()
{
return this.env;
}
public boolean isReadOnly()
{
return this.readOnly;
}
private int getDatabase(String name)
{
try (var stack = MemoryStack.stackPush())
{
var intPtr = stack.mallocInt(1);
int createFlag = this.readOnly ? 0 : LMDB.MDB_CREATE;
int returnCode = LMDB.mdb_dbi_open(this.ptr, name, createFlag, intPtr);
if (returnCode != LMDB.MDB_SUCCESS)
{
switch (returnCode)
{
case LMDB.MDB_DBS_FULL:
throw new RuntimeException("Error: mdb_dbi_open failed with the following error: DBS_FULL (too many open databases)");
case LMDB.MDB_NOTFOUND:
throw new RuntimeException("Error: mdb_dbi_open failed with the following error: NOTFOUND (database was not found)");
default:
throw new RuntimeException(String.format("Error: mdb_dbi_open failed with the following error code: %d", returnCode));
}
}
int dbHandle = intPtr.get(0);
return dbHandle;
}
}
public <K extends LMDBKey, V extends ILMDBValueRecipe> LMDBDatabase<K, V> getDatabase(LMDBSchema<K, V> schema)
{
var dbHandle = this.getDatabase(schema.getName());
var recipeClazz = schema.getValueRecipe();
try
{
return new LMDBDatabase<K, V>(this, dbHandle, recipeClazz);
}
catch (NoSuchMethodException e)
{
e.printStackTrace();
}
catch (IllegalAccessException e)
{
e.printStackTrace();
}
return null;
}
public void commit()
{
LMDB.mdb_txn_commit(this.ptr);
this.uncommited = false;
}
public void abort()
{
LMDB.mdb_txn_abort(this.ptr);
this.uncommited = false;
}
@Override
public void close() throws Exception
{
if (this.readOnly || this.uncommited)
{
this.abort();
}
}
}

View File

@ -0,0 +1,43 @@
package cz.tefek.test.plutodb;
import java.io.File;
import cz.tefek.plutodb.LMDBEnvironment;
import cz.tefek.plutodb.LMDBSchema;
import cz.tefek.plutodb.LMDBStringKey;
public class LMDBTest
{
public static void main(String[] args) throws Exception
{
var userSchema = new LMDBSchema<LMDBStringKey, UserData>("users", UserData.class);
try (var env = new LMDBEnvironment(new File("testdb"), 80, 1024 * 1024 * 256))
{
try (var txn = env.createTransaction())
{
var db = txn.getDatabase(userSchema);
var data = new UserData();
data.money = -789;
data.keks = 123456;
data.text = "Hello world!";
data.keys = 0x12C0FFEE;
db.put(LMDBStringKey.from("pepega"), data);
txn.commit();
}
try (var txn = env.createTransaction(true))
{
var db = txn.getDatabase(userSchema);
var data = db.get(LMDBStringKey.from("pepega"));
System.out.println(data);
}
}
}
}

View File

@ -0,0 +1,44 @@
package cz.tefek.test.plutodb;
import java.nio.ByteBuffer;
import cz.tefek.plutodb.ILMDBValueRecipe;
public class UserData implements ILMDBValueRecipe
{
public long money;
public long keks;
public String text;
public int keys;
@Override
public void serialize(ByteBuffer output)
{
output.putLong(this.money);
output.putLong(this.keks);
ILMDBValueRecipe.putUTF8(this.text, output);
output.putInt(this.keys);
}
@Override
public void deserialize(ByteBuffer input)
{
this.money = input.getLong();
this.keks = input.getLong();
this.text = ILMDBValueRecipe.getUTF8(input);
this.keys = input.getInt();
}
@Override
public String toString()
{
return "UserData [money=" + this.money + ", keks=" + this.keks + ", text=" + this.text + ", keys=" + this.keys + "]";
}
@Override
public int sizeOf()
{
return Long.BYTES + Long.BYTES + ILMDBValueRecipe.sizeOfUTF8(this.text) + Integer.BYTES;
}
}

24
plutoframebuffer/pom.xml Normal file
View File

@ -0,0 +1,24 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cz.tefek</groupId>
<artifactId>plutoframebuffer</artifactId>
<version>0.1</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>cz.tefek</groupId>
<artifactId>plutostatic</artifactId>
<version>0.3</version>
</dependency>
<dependency>
<groupId>cz.tefek</groupId>
<artifactId>plutotexturing</artifactId>
<version>0.1</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,84 @@
package cz.tefek.pluto.engine.graphics.gl.fbo;
import java.util.ArrayList;
import java.util.List;
import org.lwjgl.opengl.GL33;
import cz.tefek.io.pluto.debug.Logger;
import cz.tefek.io.pluto.debug.SmartSeverity;
public class Framebuffer
{
private int id;
private List<FramebufferTexture> textures;
private FramebufferDepthTexture depthTexture;
public Framebuffer()
{
this.id = GL33.glGenFramebuffers();
this.textures = new ArrayList<>(GL33.glGetInteger(GL33.GL_MAX_COLOR_ATTACHMENTS));
Logger.logf(SmartSeverity.ADDED, "Framebuffer ID %d created.\n", this.id);
}
public void bind()
{
GL33.glBindFramebuffer(GL33.GL_FRAMEBUFFER, this.id);
}
public void unbind()
{
GL33.glBindFramebuffer(GL33.GL_FRAMEBUFFER, 0);
}
public void delete()
{
GL33.glDeleteFramebuffers(this.id);
Logger.logf(SmartSeverity.REMOVED, "Framebuffer ID %d deleted.\n", this.id);
this.id = 0;
}
public void addTexture(FramebufferTexture texture)
{
this.bind();
texture.bind();
GL33.glFramebufferTexture2D(GL33.GL_FRAMEBUFFER, GL33.GL_COLOR_ATTACHMENT0
+ this.textures.size(), texture.getType(), texture.getID(), 0);
this.textures.add(texture);
}
public void removeAllTextures()
{
this.bind();
for (int i = this.textures.size() - 1; i > 0; --i)
{
var texture = this.textures.get(i);
GL33.glFramebufferTexture2D(GL33.GL_FRAMEBUFFER, GL33.GL_COLOR_ATTACHMENT0 + i, texture.getType(), 0, 0);
}
this.textures.clear();
}
public void setDepthTexture(FramebufferDepthTexture depthTexture)
{
this.bind();
depthTexture.bind();
GL33.glFramebufferTexture2D(GL33.GL_FRAMEBUFFER, GL33.GL_DEPTH_STENCIL_ATTACHMENT, depthTexture.getType(), depthTexture.getID(), 0);
this.depthTexture = depthTexture;
}
public List<FramebufferTexture> getTextures()
{
return this.textures;
}
public FramebufferDepthTexture getDepthTexture()
{
return this.depthTexture;
}
}

View File

@ -0,0 +1,21 @@
package cz.tefek.pluto.engine.graphics.gl.fbo;
import org.lwjgl.opengl.GL33;
import cz.tefek.pluto.engine.graphics.texture.MagFilter;
import cz.tefek.pluto.engine.graphics.texture.MinFilter;
import cz.tefek.pluto.engine.graphics.texture.WrapMode;
public class FramebufferDepthTexture extends FramebufferTexture
{
public FramebufferDepthTexture(int width, int height, MagFilter magFilter, MinFilter minFilter, WrapMode wrapU, WrapMode wrapV)
{
super(width, height, magFilter, minFilter, wrapU, wrapV);
}
@Override
public void writeData(long address)
{
GL33.glTexImage2D(this.type, 0, GL33.GL_DEPTH24_STENCIL8, this.width, this.height, 0, GL33.GL_DEPTH_STENCIL, GL33.GL_UNSIGNED_INT_24_8, address);
}
}

View File

@ -0,0 +1,34 @@
package cz.tefek.pluto.engine.graphics.gl.fbo;
import org.lwjgl.system.MemoryUtil;
import cz.tefek.pluto.engine.graphics.texture.MagFilter;
import cz.tefek.pluto.engine.graphics.texture.MinFilter;
import cz.tefek.pluto.engine.graphics.texture.WrapMode;
import cz.tefek.pluto.engine.graphics.texture.texture2d.RectangleTexture;
public class FramebufferTexture extends RectangleTexture
{
public FramebufferTexture(int width, int height, MagFilter magFilter, MinFilter minFilter, WrapMode wrapU, WrapMode wrapV)
{
this.bind();
this.setFilteringOptions(magFilter, minFilter);
this.setWrapOptions(wrapU, wrapV);
this.resize(width, height);
}
public FramebufferTexture(int width, int height)
{
this(width, height, MagFilter.LINEAR, MinFilter.LINEAR, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE);
}
public void resize(int width, int height)
{
this.bind();
this.width = width;
this.height = height;
this.writeData(MemoryUtil.NULL);
}
}

37
plutogui/pom.xml Normal file
View File

@ -0,0 +1,37 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cz.tefek</groupId>
<artifactId>plutogui</artifactId>
<version>0.2</version>
<name>plutogui</name>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>cz.tefek</groupId>
<artifactId>plutotexturing</artifactId>
<version>0.1</version>
</dependency>
<dependency>
<groupId>cz.tefek</groupId>
<artifactId>plutomesher</artifactId>
<version>0.2</version>
</dependency>
<dependency>
<groupId>cz.tefek</groupId>
<artifactId>plutoshader</artifactId>
<version>0.3</version>
</dependency>
<dependency>
<groupId>cz.tefek</groupId>
<artifactId>plutospritesheet</artifactId>
<version>0.2</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,67 @@
package cz.tefek.pluto.engine.graphics;
import cz.tefek.io.asl.resource.ResourceAddress;
import cz.tefek.io.asl.resource.ResourceSubscriber;
import cz.tefek.io.modloader.Mod;
import cz.tefek.io.modloader.ModEntry;
import cz.tefek.io.modloader.ModLoaderCore;
import cz.tefek.io.modloader.event.ModPreLoad;
import cz.tefek.io.modloader.event.ModPreLoadEvent;
import cz.tefek.io.modloader.event.ModUnload;
import cz.tefek.io.modloader.event.ModUnloadEvent;
import cz.tefek.io.pluto.debug.Logger;
import cz.tefek.pluto.engine.graphics.font.FontManager;
import cz.tefek.pluto.engine.graphics.font.FontShader;
import cz.tefek.pluto.engine.graphics.texture.MagFilter;
import cz.tefek.pluto.engine.graphics.texture.MinFilter;
import cz.tefek.pluto.engine.graphics.texture.WrapMode;
import cz.tefek.pluto.engine.graphics.texture.texture2d.RectangleTexture;
import cz.tefek.pluto.engine.gui.font.FontRenderer;
import cz.tefek.pluto.engine.shader.RenderShaderBuilder;
/**
* @author 493msi
*
*/
@ModEntry(modid = PlutoGUIMod.MOD_ID, displayName = "Pluto Engine GUI Renderer", author = "Tefek", build = 2, dependencies = { PlutoSpriteSheetMod.class }, clientSupport = true, serverSupport = false, version = "0.2", description = "Everything GUI of PlutoEngine.")
public class PlutoGUIMod
{
public static final String MOD_ID = "plutogui";
public static Mod instance;
public static ResourceSubscriber subscriber;
public static RectangleTexture uiElementsAtlas;
private static FontShader fontShader;
@ModPreLoad
public static void preLoad(ModPreLoadEvent event)
{
instance = ModLoaderCore.getModByID(MOD_ID);
subscriber = instance.getDefaultResourceSubscriber();
Logger.log("Intializing " + MOD_ID + "...");
fontShader = new RenderShaderBuilder(subscriber, "shaders.VertexFontShader#glsl", "shaders.FragmentFontShader#glsl").build(FontShader.class, false);
// Load the default font
FontManager.loadFont(new ResourceAddress(subscriber, "font.default"));
FontRenderer.load(fontShader);
uiElementsAtlas = new RectangleTexture();
uiElementsAtlas.load(new ResourceAddress(subscriber, "gui.elements#png"), MagFilter.NEAREST, MinFilter.NEAREST, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE);
}
@ModUnload
public static void unload(ModUnloadEvent unloadEvent)
{
uiElementsAtlas.delete();
FontManager.unloadAll();
FontRenderer.unload();
fontShader.dispose();
}
}

View File

@ -0,0 +1,102 @@
package cz.tefek.pluto.engine.graphics.font;
import java.util.HashMap;
import java.util.Map;
import java.io.BufferedReader;
import java.nio.file.Files;
import cz.tefek.io.asl.resource.ResourceAddress;
import cz.tefek.io.pluto.debug.Logger;
import cz.tefek.io.pluto.debug.SmartSeverity;
import cz.tefek.pluto.engine.graphics.texture.MagFilter;
import cz.tefek.pluto.engine.graphics.texture.MinFilter;
import cz.tefek.pluto.engine.graphics.texture.Texture;
import cz.tefek.pluto.engine.graphics.texture.WrapMode;
import cz.tefek.pluto.engine.graphics.texture.texture2d.RectangleTexture;
import cz.tefek.pluto.engine.gui.font.CharacterInfo;
import cz.tefek.pluto.engine.gui.font.Font;
public class FontManager
{
private static Map<String, Font> fonts = new HashMap<>();
public static void loadFont(ResourceAddress address)
{
String fontname = null;
int width = 0;
int height = 0;
var def = new HashMap<Character, CharacterInfo>();
int row = 0;
try (BufferedReader br = Files.newBufferedReader(address.copy().branch("definitions").fileExtension("txt").toNIOPath()))
{
String line;
while ((line = br.readLine()) != null)
{
if (line.startsWith("//"))
{
continue;
}
if (row == 0)
{
String[] fontinfo;
fontinfo = line.split(",");
fontname = fontinfo[0];
String[] dim = fontinfo[1].split("x");
width = Integer.parseInt(dim[0]);
height = Integer.parseInt(dim[1]);
}
if (row > 0)
{
String[] offs = null;
offs = line.split(" ")[1].split(";");
def.put(line.charAt(0), new CharacterInfo(row - 1, Integer.parseInt(offs[0]), Integer.parseInt(offs[1])));
}
row++;
}
br.close();
}
catch (Exception e)
{
Logger.log(SmartSeverity.ERROR, "Could not load font: " + address.toString());
e.printStackTrace();
}
Font font = new Font(fontname, width, height, def);
RectangleTexture texture = new RectangleTexture();
texture.load(address.copy().branch("tex").fileExtension("png"), MagFilter.NEAREST, MinFilter.LINEAR, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE);
font.setTexture(texture);
fonts.put(fontname, font);
}
public static void unloadAll()
{
fonts.values().stream().map(Font::getTexture).forEach(Texture::delete);
fonts.clear();
}
public static Font getFontByName(String fontname)
{
var font = fonts.get(fontname);
if (font == null)
{
Logger.log(SmartSeverity.WARNING, "Font with name " + fontname + " could not be found, using the default one instead (if there is one).");
return fonts.get("default");
}
return font;
}
}

View File

@ -0,0 +1,41 @@
package cz.tefek.pluto.engine.graphics.font;
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.ReservedAttributes;
import cz.tefek.pluto.engine.shader.ShaderBase;
import cz.tefek.pluto.engine.shader.ShaderProgram;
import cz.tefek.pluto.engine.shader.VertexArrayAttribute;
import cz.tefek.pluto.engine.shader.uniform.Uniform;
import cz.tefek.pluto.engine.shader.uniform.UniformBoolean;
import cz.tefek.pluto.engine.shader.uniform.UniformMat4;
import cz.tefek.pluto.engine.shader.uniform.UniformVec2;
import cz.tefek.pluto.engine.shader.uniform.UniformVec4;
import cz.tefek.pluto.engine.shader.uniform.auto.AutoViewportProjection;
@ShaderProgram
public final class FontShader extends ShaderBase
{
@AutoViewportProjection
@Uniform(name = "projection")
public UniformMat4 projectionMatrix;
@Uniform(name = "transformation")
public UniformMat4 transformationMatrix;
@Uniform
public UniformVec2 uvBase;
@Uniform
public UniformVec2 uvDelta;
@Uniform
public UniformVec4 recolor;
@Uniform
public UniformBoolean italic;
@VertexArrayAttribute(ReservedAttributes.POSITION)
public int position;
@VertexArrayAttribute(ReservedAttributes.UV)
public int uvCoords;
}

View File

@ -0,0 +1,6 @@
package cz.tefek.pluto.engine.gui;
public interface IGUIPipeline
{
void flush();
}

View File

@ -0,0 +1,6 @@
package cz.tefek.pluto.engine.gui;
public interface IGUIRenderer
{
void flush();
}

View File

@ -0,0 +1,30 @@
package cz.tefek.pluto.engine.gui.font;
public class CharacterInfo
{
int leftOffs;
private int rightOffs;
int number;
public CharacterInfo(int number, int leftOffs, int rightOffs)
{
this.number = number;
this.leftOffs = leftOffs;
this.rightOffs = rightOffs;
}
public int getLeftOffset()
{
return this.leftOffs;
}
public int getNumber()
{
return this.number;
}
public int getRightOffset()
{
return this.rightOffs;
}
}

View File

@ -0,0 +1,59 @@
package cz.tefek.pluto.engine.gui.font;
import java.util.HashMap;
import cz.tefek.pluto.engine.graphics.texture.texture2d.RectangleTexture;
public class Font
{
private String name;
private int width;
private int height;
private HashMap<Character, CharacterInfo> definitions;
private RectangleTexture texture;
public Font(String name, int width, int height, HashMap<Character, CharacterInfo> def)
{
this.name = name;
this.width = width;
this.height = height;
this.definitions = def;
}
public String getName()
{
return this.name;
}
public void setName(String name)
{
this.name = name;
}
public int getTextureWidth()
{
return this.width;
}
public int getTextureHeight()
{
return this.height;
}
public RectangleTexture getTexture()
{
return this.texture;
}
public void setTexture(RectangleTexture texture)
{
this.texture = texture;
this.width = texture.getWidth();
this.height = texture.getHeight();
}
public HashMap<Character, CharacterInfo> getDefinitions()
{
return this.definitions;
}
}

View File

@ -0,0 +1,121 @@
package cz.tefek.pluto.engine.gui.font;
import cz.tefek.pluto.engine.graphics.font.FontManager;
public class FontHelper
{
public static int calcStringWidth(Object string, String fontname, float relativeSize)
{
Font font = FontManager.getFontByName(fontname);
if (font == null)
{
System.out.println("Font doesn't exist: " + fontname);
return -1;
}
float absoluteCharWidth = 16;
String text = string.toString();
int maxW = 0;
int totalSpacing = 0;
for (int i = 0; i < text.length(); i++)
{
if (text.length() > i + 1)
{
if (text.charAt(i) == '\\' && text.charAt(i + 1) == '&')
{
continue;
}
}
// &c[0xff770077]
if (text.length() > i + 13)
{
if (text.charAt(i) == '&' && text.charAt(i + 1) == 'c' && text.charAt(i + 2) == '[' && text.charAt(i + 13) == ']')
{
char c = '0';
char cBef = '0';
if (i > 0)
{
c = text.charAt(i - 1);
}
if (i > 1)
{
cBef = text.charAt(i - 2);
}
if (c != '\\' || cBef == '\\' && c == '\\')
{
i += 13;
continue;
}
}
}
if (text.length() > i + 2)
{
if (text.charAt(i) == '&' && text.charAt(i + 1) == 'i')
{
char c = '0';
char cBef = '0';
if (i > 0)
{
c = text.charAt(i - 1);
}
if (i > 1)
{
cBef = text.charAt(i - 2);
}
if (c != '\\' || cBef == '\\' && c == '\\')
{
i += 2;
continue;
}
}
}
if (text.charAt(i) == '\n')
{
totalSpacing = 0;
}
else if (text.charAt(i) == ' ')
{
totalSpacing += 12 * relativeSize;
}
else
{
CharacterInfo charInf = font.getDefinitions().get(text.charAt(i));
if (charInf == null)
{
charInf = font.getDefinitions().get('?');
}
totalSpacing -= charInf.getLeftOffset() * relativeSize - relativeSize;
totalSpacing += absoluteCharWidth * relativeSize;
totalSpacing -= charInf.getRightOffset() * relativeSize - 1;
}
maxW = Math.max(maxW, totalSpacing);
}
return maxW;
}
public static int calcStringHeight(Object string, float relSize)
{
int size = (int) (30f * relSize * string.toString().split("\n").length);
return size;
}
}

View File

@ -0,0 +1,311 @@
package cz.tefek.pluto.engine.gui.font;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.opengl.GL11;
import cz.tefek.pluto.engine.graphics.font.FontManager;
import cz.tefek.pluto.engine.graphics.font.FontShader;
import cz.tefek.pluto.engine.graphics.gl.DrawMode;
import cz.tefek.pluto.engine.graphics.gl.vao.QuadPresets;
import cz.tefek.pluto.engine.graphics.gl.vao.VertexArray;
import cz.tefek.pluto.engine.math.TransformationMatrix;
public class FontRenderer
{
private static final int LINE_HEIGHT = 30;
private static final int SPACE_WIDTH = 12;
private static final int CHAR_WIDTH = 16;
private static final int CHAR_HEIGHT = 24;
private static VertexArray charVAO;
private static FontShader fontShader;
public static void load(FontShader defaultFontShaderIn)
{
charVAO = QuadPresets.basicNoNeg();
fontShader = defaultFontShaderIn;
}
public static void unload()
{
if (charVAO != null)
{
charVAO.delete();
}
}
public static void prepareInstance(Font font)
{
GL11.glEnable(GL11.GL_BLEND);
fontShader.start();
charVAO.bind();
charVAO.enableAllAttributes();
font.getTexture().bind();
}
public static void drawString(float xPos, float yPos, Object string, Object color, float relativeSize, String fontname, boolean isShadow)
{
Font font = FontManager.getFontByName(fontname);
if (font == null)
{
System.err.println("Font doesn't exist: " + fontname);
return;
}
float charWidth = CHAR_WIDTH * relativeSize;
float charHeight = CHAR_HEIGHT * relativeSize;
float lineHeight = LINE_HEIGHT * relativeSize;
float spaceWidth = SPACE_WIDTH * relativeSize;
prepareInstance(font);
color(color);
String text = String.valueOf(string);
float drawX = xPos;
float drawY = yPos;
for (int characterIndex = 0; characterIndex < text.length(); characterIndex++)
{
int column = 0;
int row = 0;
var currentChar = text.charAt(characterIndex);
if (text.length() > characterIndex + 1)
{
var nextChar = text.charAt(characterIndex + 1);
if (currentChar == '\\' && nextChar == '&')
{
continue;
}
// Inline coloring (tm) :) -> &c[0xff770077]
if (text.length() > characterIndex + 13)
{
if (currentChar == '&' && nextChar == 'c' && text.charAt(characterIndex + 2) == '[' && text.charAt(characterIndex + 13) == ']')
{
char c = '0';
char cBef = '0';
if (characterIndex > 0)
{
c = text.charAt(characterIndex - 1);
}
if (characterIndex > 1)
{
cBef = text.charAt(characterIndex - 2);
}
if (c != '\\' || cBef == '\\' && c == '\\')
{
if (!isShadow)
{
char[] col = new char[10];
text.getChars(characterIndex + 3, characterIndex + 13, col, 0);
String clr = String.valueOf(col);
color(clr);
}
characterIndex += 13;
continue;
}
}
}
if (text.length() > characterIndex + 2)
{
if (currentChar == '&' && nextChar == 'i' && (text.charAt(characterIndex + 2) == '1' || text.charAt(characterIndex + 2) == '0'))
{
char c = '0';
char cBef = '0';
if (characterIndex > 0)
{
c = text.charAt(characterIndex - 1);
}
if (characterIndex > 1)
{
cBef = text.charAt(characterIndex - 2);
}
if (c != '\\' || cBef == '\\' && c == '\\')
{
if (text.charAt(characterIndex + 2) == '1')
{
italic(true);
}
else
{
italic(false);
}
characterIndex += 2;
continue;
}
}
}
}
float shift = 0;
switch (currentChar)
{
case '\n':
color(color);
drawX = xPos;
drawY += lineHeight;
continue;
case ' ':
drawX += spaceWidth;
continue;
case 'g':
case 'y':
case 'p':
case 'j':
shift = 6 * relativeSize;
break;
default:
break;
}
var fontDefs = font.getDefinitions();
var charInf = fontDefs.get(currentChar);
if (charInf == null)
{
charInf = fontDefs.get('?');
}
var atlasIndex = charInf.getNumber();
row = atlasIndex / CHAR_WIDTH;
column = atlasIndex % CHAR_WIDTH;
// Position of the current character in the texture atlas in pixels
float u = column * CHAR_WIDTH;
float v = row * CHAR_HEIGHT;
// Offset from the left
drawX -= charInf.getLeftOffset() * relativeSize;
float posY = shift + drawY;
fontShader.uvBase.load(u, font.getTextureHeight() - v - CHAR_HEIGHT);
fontShader.uvDelta.load(CHAR_WIDTH, CHAR_HEIGHT);
Matrix4f transformation = TransformationMatrix.create(new Vector3f(drawX, posY, 0), new Vector3f(0, 0, 0), new Vector3f(charWidth, charHeight, 0));
fontShader.transformationMatrix.load(transformation);
charVAO.draw(DrawMode.TRIANGLES);
drawX += charWidth;
drawX -= charInf.getRightOffset() * relativeSize;
drawX += relativeSize;
}
italic(false);
}
public static void color(Object color)
{
color(color, false);
}
public static void color(Object color, boolean darker)
{
float dark = 0;
if (darker)
{
dark = 0.35f;
}
if (color instanceof float[])
{
float[] c = (float[]) color;
if (c.length == 4)
{
recolor(c[0] - dark, c[1] - dark, c[2] - dark, c[3]);
}
else if (c.length == 3)
{
recolor(c[0] - dark, c[1] - dark, c[2] - dark, 1);
}
}
if (color instanceof String)
{
String col = (String) color;
if (col.length() == 7)
{
recolor(Integer.valueOf(col.substring(1, 3), 16) / 256f - dark, Integer.valueOf(col.substring(3, 5), 16) / 256f - dark, Integer.valueOf(col.substring(5, 7), 16) / 256f - dark, 1);
}
if (col.length() == 10)
{
recolor(Integer.valueOf(col.substring(4, 6), 16) / 256f - dark, Integer.valueOf(col.substring(6, 8), 16) / 256f - dark, Integer.valueOf(col.substring(8, 10), 16) / 256f - dark, Integer.valueOf(col.substring(2, 4), 16) / 256f);
}
}
}
private static void recolor(float r, float g, float b, float a)
{
fontShader.recolor.load(r, g, b, a);
}
private static void italic(boolean useItalic)
{
fontShader.italic.load(useItalic);
}
public static void drawString(float x, float y, Object text, float r, float g, float b, float a, float size)
{
drawString(x, y, text, new float[] { r, g, b, a }, size, "default", false);
}
public static void drawString(float x, float y, Object text)
{
drawString(x, y, text, new float[] { 0, 0, 0, 1 }, 1, "default", false);
}
public static void drawString(float x, float y, Object text, float r, float g, float b, float a, float size, boolean isShadow)
{
drawString(x, y, text, new float[] { r, g, b, a }, size, "default", isShadow);
}
public static void drawString(float x, float y, Object text, float r, float g, float b, float a, float size, String fontname, boolean isShadow)
{
drawString(x, y, text, new float[] { r, g, b, a }, size, fontname, isShadow);
}
public static void drawString(float x, float y, Object text, float r, float g, float b, float a, float size, String fontname)
{
drawString(x, y, text, new float[] { r, g, b, a }, size, fontname, false);
}
public static void drawString(float xPos, float yPos, Object string, Object color, float relativeSize, String fontname)
{
drawString(xPos, yPos, string, color, relativeSize, fontname, false);
}
}

View File

@ -0,0 +1,106 @@
package cz.tefek.pluto.engine.gui.font;
import org.joml.Vector2fc;
import cz.tefek.pluto.engine.gui.IGUIPipeline;
import cz.tefek.pluto.engine.gui.IGUIRenderer;
public class FontRenderer2 implements IGUIRenderer
{
private static final FontRenderer2 INSTANCE_IMMEDIATE = new FontRenderer2();
private static final FontRenderer2 INSTANCE_DEFERRED = new FontRenderer2();
private final IGUIPipeline deferPipeline;
private static final float DEFAULT_SIZE = 24;
private float x;
private float y;
private String drawnText;
private float size = DEFAULT_SIZE;
private FontRenderer2(IGUIPipeline deferPipeline)
{
this.deferPipeline = deferPipeline;
}
private FontRenderer2()
{
this.deferPipeline = null;
}
public FontRenderer2 at(float x, float y)
{
this.x = x;
this.y = y;
return this;
}
public FontRenderer2 at(Vector2fc pos)
{
this.x = pos.x();
this.y = pos.y();
return this;
}
public FontRenderer2 size(float size)
{
this.size = size;
return this;
}
public FontRenderer2 string(Object text)
{
this.drawnText = String.valueOf(text);
return this;
}
public FontRenderer2 fstring(String format, Object... items)
{
this.drawnText = String.format(format, items);
return this;
}
public FontRenderer2 reset()
{
this.size = DEFAULT_SIZE;
this.drawnText = "<null>";
this.x = 0;
this.y = 0;
return this;
}
@Override
public void flush()
{
if (this.deferPipeline != null)
{
// Defer rendering to the pipeline
}
else
{
// Draw in immediate mode
}
}
public static FontRenderer2 immediate()
{
return INSTANCE_IMMEDIATE.reset();
}
public static FontRenderer2 deferred()
{
return INSTANCE_DEFERRED.reset();
}
public static FontRenderer2 draw()
{
return deferred();
}
}

View File

@ -0,0 +1,12 @@
package cz.tefek.pluto.engine.gui.pipeline;
import cz.tefek.pluto.engine.gui.IGUIPipeline;
public class BasicGUIPipeline implements IGUIPipeline
{
@Override
public void flush()
{
}
}

View File

@ -0,0 +1,12 @@
package cz.tefek.pluto.engine.gui.pipeline;
public enum EnumGUIPipelineCommand
{
MOVE_XY,
SCALE_XY,
MOVE_UV,
SCALE_UV,
ROTATE,
SET_TEXTURE,
DRAW;
}

99
plutoio2/pom.xml Normal file
View File

@ -0,0 +1,99 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cz.tefek</groupId>
<artifactId>plutoio2</artifactId>
<version>0.2</version>
<name>plutoio2</name>
<properties>
<maven.compiler.source>12</maven.compiler.source>
<maven.compiler.target>12</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<lwjgl.version>3.2.3</lwjgl.version>
</properties>
<profiles>
<profile>
<id>lwjgl-natives-linux-amd64</id>
<activation>
<os>
<family>unix</family>
<arch>amd64</arch>
</os>
</activation>
<properties>
<lwjgl.natives>natives-linux</lwjgl.natives>
</properties>
</profile>
<profile>
<id>lwjgl-natives-macos-amd64</id>
<activation>
<os>
<family>mac</family>
<arch>amd64</arch>
</os>
</activation>
<properties>
<lwjgl.natives>natives-macos</lwjgl.natives>
</properties>
</profile>
<profile>
<id>lwjgl-natives-windows-amd64</id>
<activation>
<os>
<family>windows</family>
<arch>amd64</arch>
</os>
</activation>
<properties>
<lwjgl.natives>natives-windows</lwjgl.natives>
</properties>
</profile>
<profile>
<id>lwjgl-natives-windows-x86</id>
<activation>
<os>
<family>windows</family>
<arch>x86</arch>
</os>
</activation>
<properties>
<lwjgl.natives>natives-windows-x86</lwjgl.natives>
</properties>
</profile>
</profiles>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-bom</artifactId>
<version>${lwjgl.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.0-jre</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,33 @@
package cz.tefek.io.asl.resource;
/**
* Allows loading a resource from the file system location defined by the
* supplied {@link ResourceAddress}.
*
* For example <tt>Resource&lt;String&gt;</tt> means you have a
* <tt>Resource</tt> that will output a String when loaded.
*
* @author 493msi
*
* @param R The type of the loaded <tt>Resource</tt>.
*/
public abstract class Resource<R>
{
protected ResourceAddress address;
protected boolean virtual;
protected R value;
public Resource(ResourceAddress raddress, boolean virtual)
{
this.address = raddress;
this.virtual = virtual;
}
public R load()
{
return this.virtual ? this.value : this.loadFromFile();
}
protected abstract R loadFromFile();
}

View File

@ -0,0 +1,317 @@
package cz.tefek.io.asl.resource;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import cz.tefek.io.modloader.ModLoaderCore;
import cz.tefek.io.pluto.debug.Logger;
import cz.tefek.io.pluto.debug.Severity;
/**
* Resource address is a universal key for all resource and file loading. You
* just need a {@link ResourceSubscriber} (which holds the root folder location)
* and a {@link String} containing the address. The address itself works like a
* Java package. For example <i>"sample.textures.test"</i> formats as
* <code>[root_folder]/sample/textures/test</code> when converted using
* <code>toPath()</code>. To define a file extension for your address, use the
* <code>fileExtension(String)</code> method. To remove the file extension use
* <code>fileExtension(null)</code>.
*
* @author 493msi
*
* @see ResourceSubscriber
*/
public class ResourceAddress
{
public static final int LIMIT = 32;
public static final int MAX_BRANCH_STRING_LENGTH = 32;
public static final String BRANCH_STRING_PATTERN = "[a-zA-Z0-9_]+";
public static final String FILE_EXTENSION_PATTERN = "[a-zA-Z0-9_]+";
public static final String RESOURCE_ADDRESS_STRING_PATTERN = "^(" + ModLoaderCore.MOD_ID_STRING_PATTERN + ")\\$((?:" + BRANCH_STRING_PATTERN + "\\.)*?" + BRANCH_STRING_PATTERN + "?)(?:#([a-zA-Z0-9_]+))?$";
public static final Pattern RESOURCE_ADDRESS_PATTERN = Pattern.compile(RESOURCE_ADDRESS_STRING_PATTERN, Pattern.CASE_INSENSITIVE);
protected final List<String> subAddress = new ArrayList<>(LIMIT);
protected ResourceSubscriber resSubscriber;
protected String fileExtension = null;
/**
* Copy constructor for internal use.
*/
protected ResourceAddress(ResourceSubscriber subscriber, List<String> subAddr, String fileExtension)
{
this.resSubscriber = subscriber;
this.subAddress.addAll(subAddr);
this.fileExtension(fileExtension);
}
public ResourceAddress(ResourceSubscriber sub, String address)
{
if (sub == null)
{
throw new IllegalArgumentException("Empty resource subscriber is not allowed.");
}
this.resSubscriber = sub;
var parts = address.split("#");
if (parts.length == 2)
{
address = parts[0];
this.fileExtension(parts[1]);
}
else if (parts.length > 2)
{
throw new IllegalArgumentException("Illegal ResourceAddress component count!");
}
if (address == null)
{
throw new IllegalArgumentException("Empty address is not allowed.");
}
String[] as = address.split("\\.");
if (as.length == 0)
{
throw new IllegalArgumentException("Zero length address is not allowed.");
}
if (as.length >= LIMIT)
{
throw new IllegalArgumentException("Address can't branch deeper more than " + (LIMIT - 1) + " times.");
}
if (as.length == 1)
{
Logger.log(Severity.WARNING, "Please do not use tier 1 addresses, so it doesn't conflict with core assets.");
}
for (int i = 0; i < as.length; i++)
{
var branch = as[i];
if (branch.length() < 1 || branch.length() > MAX_BRANCH_STRING_LENGTH)
{
throw new IllegalArgumentException("Length of branch must be higher than 0 and lower than 33, this is not an essay.");
}
if (!branch.matches("^[a-zA-Z0-9_]+$"))
{
throw new IllegalArgumentException("Wrong address branch format: " + branch);
}
this.subAddress.add(branch);
}
}
public static ResourceAddress parse(String strVal)
{
if (strVal == null)
{
return null;
}
if (!strVal.matches(RESOURCE_ADDRESS_STRING_PATTERN))
{
throw new IllegalArgumentException("Malformed resource address: " + strVal);
}
var addressComponents = strVal.split("[$#]");
if (addressComponents.length < 2 || addressComponents.length > 3)
{
throw new IllegalArgumentException("Illegal ResourceAddress component count!");
}
var mod = ModLoaderCore.getModByID(addressComponents[0]);
if (mod == null)
{
throw new IllegalArgumentException(String.format("Mod with ID %s not found!", addressComponents[0]));
}
var raddress = new ResourceAddress(mod.getDefaultResourceSubscriber(), addressComponents[1]);
if (addressComponents.length == 3)
{
raddress.fileExtension(addressComponents[2]);
}
return raddress;
}
public ResourceAddress fileExtension(String ext)
{
if (ext == null || ext == "")
{
this.fileExtension = null;
return this;
}
if (!ext.matches(FILE_EXTENSION_PATTERN))
{
throw new IllegalArgumentException("@ResourceAddress.fileExtension: Wrong file extension format. Only english alphabet, numbers and underscore are permitted.");
}
this.fileExtension = ext;
return this;
}
public boolean hasFileExtension()
{
if (this.fileExtension == null)
{
return false;
}
return !this.fileExtension.isBlank();
}
public String getFileExtension()
{
return this.fileExtension;
}
public ResourceAddress branch(String branch)
{
if (branch == null)
{
throw new NullPointerException("@ResourceAddress.branch: INPUT = NULL!");
}
if (branch.length() < 1 || branch.length() > MAX_BRANCH_STRING_LENGTH)
{
throw new IllegalArgumentException("@ResourceAddress.branch: Length of branch must be higher than 0 and lower than " + (MAX_BRANCH_STRING_LENGTH + 1) + ".");
}
if (!branch.matches(BRANCH_STRING_PATTERN))
{
throw new IllegalArgumentException("@ResourceAddress.branch: Wrong branch format. Only english alphabet, numbers and underscore are permitted.");
}
if (this.subAddress.size() >= LIMIT)
{
throw new IllegalArgumentException("@ResourceAddress.branch: Address can't branch deeper more than " + (LIMIT - 1) + " times.");
}
this.subAddress.add(branch);
return this;
}
@Override
public String toString()
{
StringBuilder sbPath = new StringBuilder(this.resSubscriber.getMod().getModID());
sbPath.append("$");
sbPath.append(this.subAddressToString());
String path = sbPath.toString();
return path;
}
public ResourceAddress copy()
{
return new ResourceAddress(this.resSubscriber, this.subAddress, this.fileExtension);
}
public String toPath()
{
StringBuilder sbPath = new StringBuilder(this.resSubscriber.getRootPath());
for (String branch : this.subAddress)
{
if (branch == null)
{
break;
}
sbPath.append("/");
sbPath.append(branch);
}
if (this.hasFileExtension())
{
sbPath.append("." + this.fileExtension);
}
String path = sbPath.toString();
return path;
}
public String subAddressToString()
{
StringBuilder sbPath = new StringBuilder();
boolean firstLoop = true;
for (String branch : this.subAddress)
{
if (branch == null)
{
break;
}
if (!firstLoop)
{
sbPath.append(".");
}
else
{
firstLoop = false;
}
sbPath.append(branch);
}
if (this.hasFileExtension())
{
sbPath.append("#");
sbPath.append(this.fileExtension);
}
return sbPath.toString();
}
public Path toNIOPath()
{
var pathBuilder = new StringBuilder(this.resSubscriber.getRootPath());
final var separator = FileSystems.getDefault().getSeparator();
pathBuilder.append(separator);
pathBuilder.append(this.subAddress.stream().collect(Collectors.joining(separator)));
if (this.hasFileExtension())
{
pathBuilder.append('.');
pathBuilder.append(this.fileExtension);
}
return Path.of(pathBuilder.toString());
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((this.fileExtension == null) ? 0 : this.fileExtension.hashCode());
result = prime * result + this.resSubscriber.hashCode();
result = prime * result + this.subAddress.hashCode();
return result;
}
}

View File

@ -0,0 +1,24 @@
package cz.tefek.io.asl.resource;
import java.io.IOException;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
public class ResourceAddressTypeAdapter extends TypeAdapter<ResourceAddress>
{
@Override
public void write(JsonWriter out, ResourceAddress value) throws IOException
{
out.value(String.valueOf(value));
}
@Override
public ResourceAddress read(JsonReader in) throws IOException
{
var strVal = in.nextString();
return "null".equalsIgnoreCase(strVal) ? null : ResourceAddress.parse(strVal);
}
}

View File

@ -0,0 +1,12 @@
package cz.tefek.io.asl.resource;
/**
* Doesn't do much right now. Just holds the default resource location.
*
* @author 493msi
*/
public class ResourceHelper
{
public static final String GLOBAL_ROOT = "";
public static final String DEFAULT_RESOURCE_ROOT = GLOBAL_ROOT + "data";
}

View File

@ -0,0 +1,47 @@
package cz.tefek.io.asl.resource;
import cz.tefek.io.modloader.Mod;
/**
* Allows access to resources using {@link ResourceAddress}</tt>. Requires a
* {@link Mod} instance to operate.
*
* @author 493msi
*/
public class ResourceSubscriber
{
private Mod owner;
private String rootFolder;
public ResourceSubscriber(Mod mod)
{
this(mod, ResourceHelper.DEFAULT_RESOURCE_ROOT);
}
public ResourceSubscriber(Mod mod, String root)
{
if (mod == null)
{
throw new IllegalArgumentException("Mod can't be null!");
}
this.owner = mod;
this.rootFolder = root;
}
public Mod getMod()
{
return this.owner;
}
public String getRootPath()
{
return this.rootFolder;
}
@Override
public int hashCode()
{
return this.owner.getModID().hashCode();
}
}

View File

@ -0,0 +1,17 @@
package cz.tefek.io.asl.resource.raid;
import cz.tefek.io.asl.resource.ResourceAddress;
public interface IIdentifiable
{
ResourceAddress getID();
default String getStringID()
{
return this.getID().toString();
}
int getRAID();
void onRegister(int raid);
}

View File

@ -0,0 +1,61 @@
package cz.tefek.io.asl.resource.raid;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Runtime Assigned ID (or Resource Address ID)
*/
public class RAID<E extends IIdentifiable> implements Iterable<E>
{
private static final int INITIAL_SIZE = 512;
private List<E> raid;
private Map<String, Integer> reverseRaid;
public RAID()
{
this.raid = new ArrayList<>(INITIAL_SIZE);
this.reverseRaid = new HashMap<>();
}
public void register(E item)
{
var address = item.getStringID();
if (this.reverseRaid.containsKey(address))
{
throw new IllegalArgumentException("Cannot register two items with the same resource ID!");
}
var pos = this.raid.size();
this.raid.add(item);
this.reverseRaid.put(address, pos);
item.onRegister(pos);
}
public E getByIntID(int id)
{
if (id < 0 || id >= this.raid.size())
{
return null;
}
return this.raid.get(id);
}
public int getIDOf(E item)
{
return this.reverseRaid.get(item.getStringID());
}
@Override
public Iterator<E> iterator()
{
return this.raid.iterator();
}
}

View File

@ -0,0 +1,59 @@
package cz.tefek.io.asl.resource.type;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import cz.tefek.io.asl.resource.Resource;
import cz.tefek.io.asl.resource.ResourceAddress;
import cz.tefek.io.asl.resource.ResourceHelper;
import cz.tefek.io.pluto.debug.Logger;
import cz.tefek.io.pluto.debug.Severity;
/**
* {@link ResourceAddress} in, {@link BufferedImage} out.
*
* @author 493msi
*/
public class ResourceImage extends Resource<BufferedImage>
{
public ResourceImage(ResourceAddress raddress, boolean virtual)
{
super(raddress, virtual);
}
public ResourceImage(ResourceAddress raddress)
{
super(raddress, false);
}
@Override
public BufferedImage loadFromFile()
{
try
{
return ImageIO.read(new File(this.address.toPath()));
}
catch (IOException e)
{
Logger.log(Severity.ERROR, "Could not load BufferedImage: " + this.address.toString() + ", will load placeholder.");
Logger.logException(e);
try
{
return ImageIO.read(new File(ResourceHelper.GLOBAL_ROOT + "data/assets/err/missingTex.png"));
}
catch (IOException e1)
{
Logger.log(Severity.ERROR, "Placeholder BufferedImage not found: " + ResourceHelper.GLOBAL_ROOT + "data/assets/err/missingTex.png");
Logger.log("This is not good! :C");
Logger.logException(e1);
}
return null;
}
}
}

View File

@ -0,0 +1,40 @@
package cz.tefek.io.asl.resource.type;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import cz.tefek.io.asl.resource.Resource;
import cz.tefek.io.asl.resource.ResourceAddress;
import cz.tefek.io.pluto.debug.Logger;
import cz.tefek.io.pluto.debug.Severity;
/**
* {@link ResourceAddress} in, {@link InputStream} out.
*
* @author 493msi
*/
public class ResourceInputStream extends Resource<InputStream>
{
public ResourceInputStream(ResourceAddress raddress)
{
super(raddress, false);
}
@Override
protected InputStream loadFromFile()
{
try
{
return new FileInputStream(this.address.toPath());
}
catch (IOException e)
{
Logger.log(Severity.EXCEPTION, "Failed to open " + this.address + "!");
Logger.log(Severity.EXCEPTION, e);
}
return null;
}
}

View File

@ -0,0 +1,78 @@
package cz.tefek.io.asl.textio;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import cz.tefek.io.asl.resource.ResourceAddress;
import cz.tefek.io.pluto.debug.Logger;
/**
* A simple text file reader. Apart from generic methods of loading, you can use
* a {@link ResourceAddress}. For writing use {@link TextOut}.
*
* @author 493msi
*/
public class TextIn
{
public static String load(URL url)
{
try
{
load(url.toURI());
}
catch (URISyntaxException e)
{
Logger.logException(e);
}
return null;
}
public static String load(URI uri)
{
try
{
return Files.readString(Paths.get(uri));
}
catch (Exception e)
{
Logger.logException(e);
}
return null;
}
public static String load(Path path)
{
try
{
return Files.readString(path);
}
catch (Exception e)
{
Logger.logException(e);
}
return null;
}
public static String loadInternal(String filename)
{
return load(TextIn.class.getResource("/" + filename));
}
public static String loadExternal(String filename)
{
return load(new File(filename).toURI());
}
public static String fromAddress(ResourceAddress address)
{
return load(address.toNIOPath());
}
}

View File

@ -0,0 +1,28 @@
package cz.tefek.io.asl.textio;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
/**
* Simplifies text writer creation. For reading use {@link TextIn}.
*
* @author 493msi
*/
public class TextOut
{
public static PrintStream createPrintStream(String filePath) throws IOException
{
PrintStream printstream = new PrintStream(createFOStream(filePath));
return printstream;
}
public static FileOutputStream createFOStream(String filePath) throws IOException
{
FileOutputStream fos = new FileOutputStream(filePath);
return fos;
}
}

View File

@ -0,0 +1,141 @@
package cz.tefek.io.modloader;
import cz.tefek.io.asl.resource.ResourceSubscriber;
import cz.tefek.io.pluto.debug.Logger;
import cz.tefek.io.pluto.pp.PlutoPackage;
/**
* Mod object. Can be used to create a {@link ResourceSubscriber}.
* {@link ModLoaderCore} automatically creates a Mod object for each class
* annotated by {@link ModEntry @ModEntry} (assuming it is registered or class
* loaded by ModClassLoader, which auto-detects and registers {@link ModEntry
* ModEntries}).
*
* @see <a href="http://pluto.tefek.cz/mod/dev/">PlutoModLoader tutorial</a> for
* more information.
*
* @author 493msi
*/
public class Mod extends PlutoPackage
{
private Class<?> mainClass;
private Object instance;
private boolean clientSupport;
private boolean serverSupport;
private ResourceSubscriber defaultResourceSubscriber;
private String rootDataFolder;
Mod(Class<?> mainClass, String rootDataFolder)
{
super(extractModID(mainClass));
if (mainClass != null)
{
ModEntry modInterface = mainClass.getDeclaredAnnotation(ModEntry.class);
if (modInterface != null)
{
this.mainClass = mainClass;
this.name = modInterface.displayName().isBlank() ? modInterface.modid() : modInterface.displayName();
this.author = modInterface.author();
this.version = modInterface.version();
this.build = modInterface.build();
this.earliestCompatibleBuild = modInterface.earliestCompatibleBuild();
this.dependencies = modInterface.dependencies();
this.description = modInterface.description();
this.iconURL = modInterface.iconURL();
this.rootDataFolder = rootDataFolder;
this.defaultResourceSubscriber = new ResourceSubscriber(this, rootDataFolder);
this.clientSupport = modInterface.clientSupport();
this.serverSupport = modInterface.serverSupport();
}
}
}
private static String extractModID(Class<?> mainClass)
{
if (mainClass != null)
{
ModEntry modInterface = mainClass.getDeclaredAnnotation(ModEntry.class);
if (modInterface != null)
{
return modInterface.modid();
}
}
return null;
}
Class<?> getMainClass()
{
return this.mainClass;
}
Object getClassInstance() throws ReflectiveOperationException
{
if (this.instance == null)
{
Logger.log("|Pluto Mod Loader| Loading mod instance: " + this.name);
this.instance = this.mainClass.getDeclaredConstructor().newInstance();
}
return this.instance;
}
public String getModID()
{
return this.id;
}
public String getModName()
{
return this.name;
}
public String getModAuthor()
{
return this.author;
}
public String getModVersion()
{
return this.version;
}
public int getModBuild()
{
return this.build;
}
public boolean isClientSupported()
{
return this.clientSupport;
}
public boolean isServerSupported()
{
return this.serverSupport;
}
public ResourceSubscriber getDefaultResourceSubscriber()
{
return this.defaultResourceSubscriber;
}
public void setRootDataFolder(String rootDataFolder)
{
this.rootDataFolder = rootDataFolder;
this.defaultResourceSubscriber = new ResourceSubscriber(this, rootDataFolder);
}
public String getRootDataFolder()
{
return this.rootDataFolder;
}
}

View File

@ -0,0 +1,152 @@
package cz.tefek.io.modloader;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import cz.tefek.io.asl.resource.ResourceHelper;
import cz.tefek.io.pluto.debug.Logger;
/**
* Class-loads all valid mods. The only requirement for the mod is to have a
* mod.jar file inside the base folder (for example
* <i>mods/spaghetti/mod.jar</i>).
*
* @author 493msi
*/
public class ModClassLoader
{
public static ArrayList<String> mods;
public static void loadJars()
{
Logger.log("[Pluto Mod Loader] Looking for installed mods.");
File modDir = new File(ResourceHelper.GLOBAL_ROOT + "mods/");
if (!modDir.exists())
{
modDir.mkdir();
Logger.log("[Pluto Mod Loader] No mod found.");
return;
}
mods = new ArrayList<>(Arrays.asList(modDir.list()));
if (mods.size() == 0)
{
Logger.log("[Pluto Mod Loader] No mod found.");
}
else
{
Logger.log("[Pluto Mod Loader] Found " + mods.size() + " mod(s) to load.");
try
{
loadAll();
}
catch (Exception e)
{
Logger.logException(e);
}
}
}
private static void loadAll() throws Exception
{
int i = 0;
if (mods.size() > 0)
{
if (mods.size() == 1)
{
Logger.log("[Pluto Mod Loader] There is one mod to load.");
}
else
{
Logger.log("[Pluto Mod Loader] There are " + mods.size() + " mods to load.");
}
for (String modname : mods)
{
var modFolder = ResourceHelper.GLOBAL_ROOT + "mods/" + modname;
var dataDir = modFolder + "/data";
if (new File(modFolder + "/mod.jar").exists())
{
var dataDirFile = new File(dataDir);
if (!dataDirFile.isDirectory())
{
dataDirFile.mkdirs();
}
String pathToJar = modFolder + "/mod.jar";
JarFile jarFile = new JarFile(pathToJar);
Enumeration<JarEntry> e = jarFile.entries();
URL[] urls = { new URL("jar:file:" + pathToJar + "!/") };
URLClassLoader sysLoader = URLClassLoader.newInstance(urls, ClassLoader.getSystemClassLoader());
while (e.hasMoreElements())
{
JarEntry je = e.nextElement();
if (je.isDirectory())
{
continue;
}
// Not sure what to do with non-java files.
// They are ignored (for now).
if (!je.getName().endsWith(".class"))
{
continue;
}
String className = je.getName().replaceAll("\\.class$", "").replace('/', '.');
Class<?> modClass = sysLoader.loadClass(className);
if (modClass.getDeclaredAnnotation(ModEntry.class) != null)
{
ModLoaderCore.registerMod(modClass, dataDir);
}
}
jarFile.close();
Logger.log("[Pluto Mod Loader] Loaded mod jar file: " + modname + "/mod.jar");
i++;
}
else
{
Logger.log("[Pluto Mod Loader] Failed to load mod jar file: " + modname + ".");
Logger.log("[Pluto Mod Loader] Reason: Missing mod.jar file.");
}
}
if (i < 1 || i == 0)
{
System.out.println("[Pluto Mod Loader] Loading mods complete, loaded " + i + " mods.");
}
else
{
System.out.println("[Pluto Mod Loader] Loading mods complete, loaded " + i + " mod.");
}
}
else
{
Logger.log("[Pluto Mod Loader] There aren't any mods, skipping initialising phase.");
}
}
}

View File

@ -0,0 +1,39 @@
package cz.tefek.io.modloader;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* The heart of any Pluto mod. Annotate your class with this, so the
* PlutoModLoader can load it. The class must be directly registered or
* processed by {@link ModClassLoader} (as an external mod).
*
* @see <a href="http://pluto.tefek.cz/mod/dev/">PlutoModLoader tutorial</a> for
* more information.
*
* @author 493msi
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface ModEntry {
String modid();
String displayName() default "";
String author() default "anonymous author";
String version() default "unknown version";
Class<?>[] dependencies() default {};
String iconURL() default "";
String description() default "No description available";
int build() default 0;
int earliestCompatibleBuild() default 0;
boolean clientSupport() default true;
boolean serverSupport() default false;
}

View File

@ -0,0 +1,117 @@
package cz.tefek.io.modloader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import cz.tefek.io.asl.resource.ResourceHelper;
import cz.tefek.io.pluto.debug.Logger;
import cz.tefek.io.pluto.debug.Severity;
/**
* Unzips mod packages from the packages folder into the mods folder. WIP
*
* @author 493msi
*/
public class ModInstaller
{
public static void unpackNewMods()
{
Logger.log("[Pluto Mod Loader] Looking for new mod packages to install.");
File f = new File(ResourceHelper.GLOBAL_ROOT + "packages/");
if (!f.exists())
{
f.mkdir();
Logger.log("[Pluto Mod Loader] Package folder does not exist, creating it and aborting unpacking.");
return;
}
ArrayList<String> files = new ArrayList<String>(Arrays.asList(f.list()));
if (files.size() == 0)
{
Logger.log("[Pluto Mod Loader] No mod package found.");
}
else
{
Logger.log("[Pluto Mod Loader] Found " + files.size() + " mod packages.");
for (String file : files)
{
Logger.log("[Pluto Mod Loader] Mod package found: " + file + ", installing it.");
try
{
extract(file);
}
catch (IOException e)
{
Logger.log(Severity.ERROR, "Unpacking of " + file + " failed!");
Logger.logException(e);
}
new File(ResourceHelper.GLOBAL_ROOT + "packages/" + file).delete();
}
}
}
private static void extract(String filepath) throws IOException
{
byte[] buffer = new byte[2048];
InputStream fileInput = null;
fileInput = new FileInputStream(ResourceHelper.GLOBAL_ROOT + "packages/" + filepath);
ZipInputStream stream = new ZipInputStream(fileInput);
String outdir = ResourceHelper.GLOBAL_ROOT + "mods/";
if (!new File(outdir).exists())
{
new File(outdir).mkdir();
}
try
{
String filename = filepath.replaceAll("\\.zip$", "");
new File(outdir + filename).mkdir();
ZipEntry entry;
while ((entry = stream.getNextEntry()) != null)
{
String outpath = outdir + filename + "/" + entry.getName();
FileOutputStream output = null;
try
{
output = new FileOutputStream(outpath);
int len = 0;
while ((len = stream.read(buffer)) > 0)
{
output.write(buffer, 0, len);
}
}
finally
{
if (output != null)
{
output.close();
}
}
}
}
finally
{
stream.close();
}
}
}

View File

@ -0,0 +1,270 @@
package cz.tefek.io.modloader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import cz.tefek.io.asl.resource.ResourceHelper;
import cz.tefek.io.modloader.event.ModLoad;
import cz.tefek.io.modloader.event.ModLoadEvent;
import cz.tefek.io.modloader.event.ModPostLoad;
import cz.tefek.io.modloader.event.ModPostLoadEvent;
import cz.tefek.io.modloader.event.ModPreLoad;
import cz.tefek.io.modloader.event.ModPreLoadEvent;
import cz.tefek.io.modloader.event.ModUnload;
import cz.tefek.io.modloader.event.ModUnloadEvent;
import cz.tefek.io.pluto.debug.Logger;
import cz.tefek.io.pluto.debug.SmartSeverity;
import cz.tefek.pluto.eventsystem.staticmode.StaticPlutoEventManager;
public class ModLoaderCore
{
public static final String MOD_ID_STRING_PATTERN = "[a-z0-9_]+";
public static final String FULL_MOD_ID_STRING_PATTERN = "^" + MOD_ID_STRING_PATTERN + "$";
static ModLoadingPhase loadingPhase = ModLoadingPhase.WAITING;
private static final List<Mod> modArchive = new ArrayList<>();
private static final LinkedList<Mod> loadBuffer = new LinkedList<>();
public static void registerMod(Class<?> modClass, String modDataRoot)
{
if (loadingPhase != ModLoadingPhase.WAITING && loadingPhase != ModLoadingPhase.CLASSLOADING)
{
Logger.log(SmartSeverity.ERROR, "Cannot register mod during loading phase " + loadingPhase.name() + "!");
return;
}
if (getModByMainClass(modClass) != null)
{
Logger.log(SmartSeverity.WARNING, "[Pluto Mod Loader] Mod class " + modClass.getCanonicalName() + " is already registered, skipping it.");
return;
}
if (modDataRoot == null)
{
modDataRoot = ResourceHelper.DEFAULT_RESOURCE_ROOT;
}
var registeredMod = loadBuffer.stream().filter(presentMod -> presentMod.getMainClass().equals(modClass)).findAny();
if (registeredMod.isPresent())
{
if (modDataRoot != null)
{
var mod = registeredMod.get();
mod.setRootDataFolder(modDataRoot);
}
return;
}
Mod mod = new Mod(modClass, modDataRoot);
if (!mod.getModID().matches(FULL_MOD_ID_STRING_PATTERN))
{
Logger.log(SmartSeverity.WARNING, "[Pluto Mod Loader] Modid " + mod.getModID() + " contains invalid characters (or none at all), mod will not be loaded.");
Logger.log(SmartSeverity.WARNING, "[Pluto Mod Loader] Only lowercase letters (a to z) and numbers (0 to 9) are allowed characters.");
return;
}
var deps = mod.getDependencies();
for (var dependency : deps)
{
var registeredDependency = loadBuffer.stream().filter(presentMod -> presentMod.getMainClass().equals(dependency)).findAny();
if (registeredDependency.isPresent())
{
continue;
}
registerMod(dependency);
}
loadBuffer.add(mod);
}
public static void registerMod(Class<?> modClass)
{
registerMod(modClass, null);
}
public static List<Mod> getAllMods()
{
return Collections.unmodifiableList(modArchive);
}
public static Mod getModByID(String id)
{
for (Mod mod : modArchive)
{
if (mod.getModID().equals(id))
{
return mod;
}
}
return null;
}
private static Mod getModByMainClass(Class<?> modClass)
{
for (Mod mod : modArchive)
{
if (modClass.equals(mod.getMainClass()))
{
return mod;
}
}
return null;
}
public static void loadProcedure()
{
loadingPhase = ModLoadingPhase.PREPARING;
StaticPlutoEventManager.registerEvent(ModPreLoad.class);
StaticPlutoEventManager.registerEvent(ModLoad.class);
StaticPlutoEventManager.registerEvent(ModPostLoad.class);
StaticPlutoEventManager.registerEvent(ModUnload.class);
Logger.log("[Pluto Mod Loader] Number of manually added mods: " + modArchive.size());
loadingPhase = ModLoadingPhase.UPACKING;
ModInstaller.unpackNewMods();
loadingPhase = ModLoadingPhase.CLASSLOADING;
ModClassLoader.loadJars();
loadingPhase = ModLoadingPhase.INITIALIZING;
while (loadBuffer.peek() != null)
{
var mod = loadBuffer.removeFirst();
StaticPlutoEventManager.registerEventHandler(mod.getMainClass());
modArchive.add(mod);
}
if (!modArchive.isEmpty())
{
Logger.log("[Pluto Mod Loader] Initializing mod(s)...");
initMods();
if (loadingPhase == ModLoadingPhase.FINISHING)
{
Logger.log("[Pluto Mod Loader] Initializing mod(s) finished.");
}
else
{
Logger.log("[Pluto Mod Loader] Initializing mod(s) was canceled due to error(s).");
}
}
}
static void initMods()
{
while (loadingPhase != ModLoadingPhase.CANCELED && loadingPhase != ModLoadingPhase.FINISHING)
{
switch (loadingPhase)
{
case INITIALIZING:
preLoadMods();
break;
case PRELOADING:
loadMods();
break;
case LOADING:
postLoadMods();
break;
case POSTLOADING:
complete();
break;
default:
break;
}
}
}
public static void unloadProcedure()
{
Logger.log("[Pluto Mod Loader] Unloading all mods.");
StaticPlutoEventManager.fireEvent(ModUnload.class, new ModUnloadEvent());
modArchive.clear();
}
private static void preLoadMods()
{
loadingPhase = ModLoadingPhase.PRELOADING;
try
{
ModPreLoadEvent preLoadData = new ModPreLoadEvent();
preLoadData.mods = modArchive;
StaticPlutoEventManager.fireEvent(ModPreLoad.class, preLoadData);
}
catch (Exception e)
{
Logger.log("[Pluto Mod Loader] Problem encountered while preloading mods.");
Logger.log("[Pluto Mod Loader] Mod loading stopped.");
Logger.logException(e);
cancelLoading();
}
}
private static void loadMods()
{
loadingPhase = ModLoadingPhase.LOADING;
try
{
ModLoadEvent loadData = new ModLoadEvent();
StaticPlutoEventManager.fireEvent(ModLoad.class, loadData);
}
catch (Exception e)
{
Logger.log("[Pluto Mod Loader] Problem encountered while loading mods.");
Logger.log("[Pluto Mod Loader] Mod loading stopped.");
Logger.logException(e);
cancelLoading();
}
}
private static void postLoadMods()
{
loadingPhase = ModLoadingPhase.POSTLOADING;
try
{
ModPostLoadEvent postLoadData = new ModPostLoadEvent();
StaticPlutoEventManager.fireEvent(ModPostLoad.class, postLoadData);
}
catch (Exception e)
{
Logger.log("[Pluto Mod Loader] Problem encountered while loading mods.");
Logger.log("[Pluto Mod Loader] Mod loading stopped.");
Logger.logException(e);
cancelLoading();
}
}
private static void complete()
{
loadingPhase = ModLoadingPhase.FINISHING;
}
private static void cancelLoading()
{
loadingPhase = ModLoadingPhase.CANCELED;
}
}

View File

@ -0,0 +1,17 @@
package cz.tefek.io.modloader;
public enum ModLoadingPhase
{
UPACKING,
PREPARING,
INITIALIZING,
WAITING,
PRELOADING,
LOADING,
POSTLOADING,
FINISHING,
CANCELED,
INSTANTIATING,
CLASSLOADING,
UNLOADING;
}

View File

@ -0,0 +1,21 @@
package cz.tefek.io.modloader.event;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import cz.tefek.pluto.eventsystem.staticmode.StaticPlutoEvent;
@Retention(RUNTIME)
@Target(METHOD)
@StaticPlutoEvent(passingParamClass = ModLoadEvent.class)
/**
* @author 493msi
*
*/
public @interface ModLoad
{
}

View File

@ -0,0 +1,15 @@
package cz.tefek.io.modloader.event;
import cz.tefek.io.modloader.ModEntry;
import cz.tefek.pluto.eventsystem.EventData;
/**
* Instances are passed to {@link ModEntry ModEntries} on load. Currently does
* nothing.
*
* @author 493msi
*
*/
public class ModLoadEvent extends EventData
{
}

View File

@ -0,0 +1,21 @@
package cz.tefek.io.modloader.event;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import cz.tefek.pluto.eventsystem.staticmode.StaticPlutoEvent;
@Retention(RUNTIME)
@Target(METHOD)
@StaticPlutoEvent(passingParamClass = ModPostLoadEvent.class)
/**
* @author 493msi
*
*/
public @interface ModPostLoad
{
}

View File

@ -0,0 +1,18 @@
package cz.tefek.io.modloader.event;
import java.util.ArrayList;
import java.util.List;
import cz.tefek.io.modloader.ModEntry;
import cz.tefek.pluto.eventsystem.EventData;
/**
* Instances are passed to {@link ModEntry ModEntries} on post-load.
*
* @author 493msi
*/
public class ModPostLoadEvent extends EventData
{
// TODO Cross-mod messaging
public final List<String> crossMessages = new ArrayList<String>();
}

View File

@ -0,0 +1,21 @@
package cz.tefek.io.modloader.event;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import cz.tefek.pluto.eventsystem.staticmode.StaticPlutoEvent;
@Retention(RUNTIME)
@Target(METHOD)
@StaticPlutoEvent(passingParamClass = ModPreLoadEvent.class)
/**
* @author 493msi
*
*/
public @interface ModPreLoad
{
}

View File

@ -0,0 +1,19 @@
package cz.tefek.io.modloader.event;
import java.util.List;
import cz.tefek.io.modloader.Mod;
import cz.tefek.io.modloader.ModEntry;
import cz.tefek.pluto.eventsystem.EventData;
/**
* Instances are passed to {@link ModEntry ModEntries} on load. Carries a list
* of all {@link Mod} objects.
*
* @author 493msi
*
*/
public class ModPreLoadEvent extends EventData
{
public List<Mod> mods;
}

View File

@ -0,0 +1,20 @@
package cz.tefek.io.modloader.event;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import cz.tefek.pluto.eventsystem.staticmode.StaticPlutoEvent;
@Retention(RUNTIME)
@Target(METHOD)
@StaticPlutoEvent(passingParamClass = ModUnloadEvent.class)
/**
* @author 493msi
*
*/
public @interface ModUnload {
}

View File

@ -0,0 +1,15 @@
package cz.tefek.io.modloader.event;
import cz.tefek.io.modloader.ModEntry;
import cz.tefek.pluto.eventsystem.EventData;
/**
* Instances are passed to {@link ModEntry ModEntries} on mod unload.
*
* @author 493msi
*
*/
public class ModUnloadEvent extends EventData
{
}

View File

@ -0,0 +1,8 @@
package cz.tefek.io.pluto.debug;
public interface ISeverity
{
String getDisplayName();
boolean isStdErr();
}

View File

@ -0,0 +1,98 @@
package cz.tefek.io.pluto.debug;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import cz.tefek.io.asl.resource.ResourceHelper;
import cz.tefek.io.asl.textio.TextOut;
/**
* Logger. 'nuff said.
*
* @author 493msi
*
*/
public class Logger
{
static OutputStream stdout;
static OutputStream stderr;
static FileOutputStream file_log;
static PrintStream stdoutStream;
static PrintStream stderrStream;
public static void setup()
{
stdout = new PrintStream(System.out);
stderr = new PrintStream(System.err);
setupFileStream();
stdoutStream = new PrintStream(new StdOutSplitStream());
stderrStream = new PrintStream(new StdErrSplitStream());
System.setOut(stdoutStream);
System.setErr(stderrStream);
}
private static void setupFileStream()
{
try
{
if (!new File(ResourceHelper.GLOBAL_ROOT + "logs").exists() || !new File(ResourceHelper.GLOBAL_ROOT + "logs").isDirectory())
{
new File(ResourceHelper.GLOBAL_ROOT + "logs").mkdirs();
}
file_log = TextOut.createFOStream(ResourceHelper.GLOBAL_ROOT + "logs/log" + System.currentTimeMillis() + ".txt");
}
catch (IOException e)
{
e.printStackTrace();
}
}
public static void close()
{
System.out.close();
}
public static void log(Object string)
{
log(Severity.NONE, string);
}
public static void logf(String string, Object... o)
{
System.out.printf(string, o);
}
public static void logNoLine(Object string)
{
System.out.print(string);
}
public static void logException(Exception e)
{
e.printStackTrace();
}
public static void log(ISeverity s, Object string)
{
(s.isStdErr() ? System.err : System.out).println(s.getDisplayName() + string);
}
public static void logf(ISeverity s, String string, Object... o)
{
(s.isStdErr() ? System.err : System.out).printf(s.getDisplayName() + string, o);
}
public static void logNoLine(ISeverity s, Object string)
{
(s.isStdErr() ? System.err : System.out).print(s.getDisplayName() + string);
}
}

View File

@ -0,0 +1,35 @@
package cz.tefek.io.pluto.debug;
/**
* Message severity.
*
* @author 493msi
*/
public enum Severity implements ISeverity
{
INFO("[INFO] ", false),
WARNING("[WARNING] ", true),
ERROR("[ERROR] ", true),
EXCEPTION("[EXCEPTION] ", true),
NONE("", false);
private String displayName;
private boolean usesStdErr;
Severity(String name, boolean usesStdErr)
{
this.displayName = name;
}
@Override
public String getDisplayName()
{
return this.displayName;
}
@Override
public boolean isStdErr()
{
return this.usesStdErr;
}
}

View File

@ -0,0 +1,38 @@
package cz.tefek.io.pluto.debug;
/**
* A more visual way to denote what's actually happening.
*
* @author 493msi
*/
public enum SmartSeverity implements ISeverity
{
ADDED("[+] ", false),
REMOVED("[-] ", false),
ZERO("[0] ", false),
INFO("[i] ", false),
WARNING("[!] ", true),
ERROR("[X] ", true);
private String displayName;
private boolean usesStdErr;
SmartSeverity(String name, boolean usesStdErr)
{
this.displayName = name;
this.usesStdErr = usesStdErr;
}
@Override
public String getDisplayName()
{
return this.displayName;
}
@Override
public boolean isStdErr()
{
return this.usesStdErr;
}
}

View File

@ -0,0 +1,53 @@
package cz.tefek.io.pluto.debug;
import java.io.IOException;
import java.io.OutputStream;
/**
* Splits one {@link OutputStream} into two to allow writing to both log file
* and console output.
*
* @author 493msi
*
*/
class StdErrSplitStream extends OutputStream
{
public StdErrSplitStream()
{
}
@Override
public void write(int b) throws IOException
{
Logger.file_log.write(b);
Logger.stderr.write(b);
}
@Override
public void write(byte[] b) throws IOException
{
Logger.file_log.write(b);
Logger.stderr.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException
{
Logger.file_log.write(b, off, len);
Logger.stderr.write(b, off, len);
}
@Override
public void flush() throws IOException
{
Logger.file_log.flush();
Logger.stderr.flush();
}
@Override
public void close() throws IOException
{
Logger.file_log.close();
}
}

View File

@ -0,0 +1,53 @@
package cz.tefek.io.pluto.debug;
import java.io.IOException;
import java.io.OutputStream;
/**
* Splits one {@link OutputStream} into two to allow writing to both log file
* and console output.
*
* @author 493msi
*
*/
class StdOutSplitStream extends OutputStream
{
public StdOutSplitStream()
{
}
@Override
public void write(int b) throws IOException
{
Logger.file_log.write(b);
Logger.stdout.write(b);
}
@Override
public void write(byte[] b) throws IOException
{
Logger.file_log.write(b);
Logger.stdout.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException
{
Logger.file_log.write(b, off, len);
Logger.stdout.write(b, off, len);
}
@Override
public void flush() throws IOException
{
Logger.file_log.flush();
Logger.stdout.flush();
}
@Override
public void close() throws IOException
{
Logger.file_log.close();
}
}

View File

@ -0,0 +1,16 @@
package cz.tefek.io.pluto.pp;
/**
* Thrown when a provided package definition is for some reason not valid.
*
* @author 493msi
*/
public class InvalidPlutoPackageException extends Exception
{
private static final long serialVersionUID = 7853852981742059946L;
public InvalidPlutoPackageException(String message, Throwable e)
{
super(message, e);
}
}

View File

@ -0,0 +1,95 @@
package cz.tefek.io.pluto.pp;
public class PlutoPackage
{
public final String id;
protected String name;
protected String version;
protected int build;
protected String description;
protected String iconURL;
protected String author;
protected Class<?>[] dependencies;
protected int earliestCompatibleBuild;
public PlutoPackage(String id)
{
this.id = id;
}
public String getName()
{
return this.name;
}
void setName(String name)
{
this.name = name;
}
public String getVersion()
{
return this.version;
}
void setVersion(String version)
{
this.version = version;
}
public int getBuild()
{
return this.build;
}
void setBuild(int build)
{
this.build = build;
}
public String getDescription()
{
return this.description;
}
void setDescription(String description)
{
this.description = description;
}
public String getIconURL()
{
return this.iconURL;
}
void setIconURL(String iconURL)
{
this.iconURL = iconURL;
}
public String getAuthor()
{
return this.author;
}
void setAuthor(String author)
{
this.author = author;
}
public Class<?>[] getDependencies()
{
return this.dependencies;
}
public boolean isBackwardsCompatibleWithBuild(int build)
{
return build > this.earliestCompatibleBuild;
}
public boolean isMod()
{
return false;
}
}

View File

@ -0,0 +1,67 @@
package cz.tefek.l10n;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import cz.tefek.io.pluto.debug.Logger;
import cz.tefek.io.pluto.debug.SmartSeverity;
public class PlutoL10n
{
private static Locale defaultLocale;
private static Map<Locale, Map<String, String>> localizations = new HashMap<>();
public static void init(Locale locale)
{
Locale.setDefault(locale);
defaultLocale = locale;
setLocale(defaultLocale);
Logger.logf(SmartSeverity.INFO, "Default locale: %s\n", defaultLocale.toString());
}
public static void registerLocale(Locale locale)
{
if (localizations.containsKey(locale))
{
return;
}
localizations.put(locale, new HashMap<>());
}
public static void setLocale(Locale locale)
{
registerLocale(locale);
Locale.setDefault(locale);
}
public static void map(Locale locale, String src, String dest)
{
registerLocale(locale);
localizations.get(locale).put(src, dest);
}
public static String get(Locale locale, String src)
{
if (!localizations.containsKey(locale))
{
if (!localizations.containsKey(defaultLocale))
{
return src;
}
return localizations.get(defaultLocale).getOrDefault(src, src);
}
return localizations.get(locale).getOrDefault(src, src);
}
public static String get(String src)
{
return get(Locale.getDefault(), src);
}
}

View File

@ -0,0 +1,10 @@
package cz.tefek.pluto.eventsystem;
/**
* @author 493msi
*
*/
public class EventData
{
}

View File

@ -0,0 +1,95 @@
package cz.tefek.pluto.eventsystem.lambda;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* A simple functional interface based event factory for objects basically
* implementing the Observer design pattern.
*
* @since 0.2
*
* @author 493msi
*/
public class LambdaEventFactory
{
/**
* A class representing a list of observing consumers.
*
* @param <T> the type of the event data
*
* @since 0.2
*
* @author 493msi
*/
public static class LambdaEvent<T>
{
private List<Consumer<T>> consumers;
private LambdaEvent()
{
this.consumers = new ArrayList<>();
}
/**
* Adds a new listener to observe this object.
*
* @param callback a functional interface representing a callback
* function
*
* @since 0.2
*
* @author 493msi
*/
public void addListener(Consumer<T> callback)
{
this.consumers.add(callback);
}
/**
* Removes a calback from the list of observers.
*
* @param callback A functional interface representing a callback
* function
*
* @since 0.2
*
* @author 493msi
*/
public void removeListener(Consumer<T> callback)
{
this.consumers.remove(callback);
}
/**
* Notifies all observers by invoking their callbacks with the specified
* value.
*
* @param value the data to distribute to all observers
*
* @since 0.2
*
* @author 493msi
*/
public void fire(T value)
{
this.consumers.forEach(c -> c.accept(value));
}
}
/**
*
* @return A new observable {@link LambdaEvent} object
*
* @param <T> the data type this event object will work with
*
* @since 0.2
*
* @author 493msi
*/
public static <T> LambdaEvent<T> createEvent()
{
return new LambdaEvent<>();
}
}

View File

@ -0,0 +1,25 @@
package cz.tefek.pluto.eventsystem.staticmode;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import cz.tefek.pluto.eventsystem.EventData;
@Retention(RUNTIME)
@Target(ANNOTATION_TYPE)
/**
* @author 493msi
*
*/
public @interface StaticPlutoEvent
{
/**
* This actually does nothing. ¯\_()_/¯ Well, you can use it for improved
* code readability.
*
*/
Class<?> passingParamClass() default EventData.class;
}

View File

@ -0,0 +1,228 @@
package cz.tefek.pluto.eventsystem.staticmode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import cz.tefek.io.pluto.debug.Logger;
import cz.tefek.io.pluto.debug.Severity;
import cz.tefek.pluto.eventsystem.EventData;
/**
* A universal event manager. Register an event {@link Annotation} of your
* choice (must be annotated with {@link StaticPlutoEvent &commat;Event}), then
* annotate some public static method and you are done! Now you can trigger the
* callbacks with ease. Multiple per-method events are possible! <i>Note that
* event callbacks require a data-passing parameter
* <tt>(extends {@link EventData})</tt>. The method will not be invoked
* otherwise!</i>
*
* @author 493msi
*
*/
public class StaticPlutoEventManager
{
private static Map<Class<? extends Annotation>, List<Method>> eventRegistry = new HashMap<Class<? extends Annotation>, List<Method>>();
private static List<Method> orphans = new ArrayList<Method>();
public static void registerEventHandler(Class<?> clazz)
{
Method[] methods = clazz.getMethods();
int methodsFound = 0;
for (Method method : methods)
{
// The callback method must be static and public!
if (!(Modifier.isStatic(method.getModifiers()) && Modifier.isPublic(method.getModifiers())))
{
continue;
}
for (Annotation annotation : method.getDeclaredAnnotations())
{
if (annotation.annotationType().getAnnotation(StaticPlutoEvent.class) != null)
{
methodsFound++;
var parentAnnotation = eventRegistry.get(annotation.annotationType());
// Find a parent annotation.
if (parentAnnotation != null)
{
parentAnnotation.add(method);
}
// No parent annotation and fix for methods with 2+ event
// annotations.
else if (!orphans.contains(method))
{
orphans.add(method);
}
}
}
}
Logger.log(Severity.INFO, "Event handler " + clazz.getCanonicalName() + " scan found " + methodsFound + " method callback(s).");
}
public static void registerEvent(Class<? extends Annotation> annotation)
{
// @Event is necessary.
if (annotation.getAnnotation(StaticPlutoEvent.class) != null)
{
if (eventRegistry.containsKey(annotation))
{
Logger.log(Severity.ERROR, "Annotation " + annotation.getCanonicalName() + " is already registered!");
return;
}
else
{
eventRegistry.put(annotation, new ArrayList<Method>());
Logger.log(Severity.INFO, "Event " + annotation.getCanonicalName() + " successfully registered!");
short retroactivelyFound = 0;
// Let's check all existing event Methods for this event.
for (Entry<Class<? extends Annotation>, List<Method>> entry : eventRegistry.entrySet())
{
// Checking the Method list for this event would make no
// sense.
if (annotation.equals(entry.getKey()))
{
continue;
}
for (Method method : entry.getValue())
{
// Just in case.
if (method.isAnnotationPresent(annotation))
{
eventRegistry.get(annotation).add(method);
retroactivelyFound++;
}
}
}
Logger.log(Severity.INFO, "Retroactive method checking found " + retroactivelyFound + " item(s).");
// Let's check the Method orphanage for some potential
// candidates.
short orphansFound = 0;
int orphansBefore = orphans.size();
List<Method> foundParents = new ArrayList<Method>();
for (Method method : orphans)
{
if (method.isAnnotationPresent(annotation))
{
foundParents.add(method);
// No duplicates.
if (!eventRegistry.get(annotation).contains(method))
{
eventRegistry.get(annotation).add(method);
orphansFound++;
}
}
}
orphans.removeAll(foundParents);
Logger.log(Severity.INFO, orphansFound + " orphan method(s) was/were bound and " + (orphansBefore - orphans.size()) + " removed from the storage!");
}
}
else
{
Logger.log(Severity.ERROR, "Annotation " + annotation.getCanonicalName() + " is not annotated with @Event, can't register it.");
}
}
public static void fireEvent(Class<? extends Annotation> event, EventData data)
{
if (event.getAnnotation(StaticPlutoEvent.class) != null)
{
List<Method> methodList = eventRegistry.get(event);
if (methodList != null)
{
for (Method m : methodList)
{
// If a method contains more than one parameter, the most
// viable one will be chosen. Also, methods with no
// parameters are not valid.
Class<?>[] params = m.getParameterTypes();
Class<?> mostSuitableParam = null;
EventData[] paramOut = new EventData[params.length];
if (params.length == 0)
{
Logger.log(Severity.WARNING, "Method " + m.toGenericString() + " has no parameters, will not be invoked by event!");
}
for (int i = 0; i < params.length; i++)
{
Class<?> parameter = params[i];
if (!EventData.class.isAssignableFrom(parameter))
{
Logger.log(Severity.ERROR, "Method " + m.toGenericString() + " contains invalid parameters. Only EventData instances are permitted.");
mostSuitableParam = null;
break;
}
if (mostSuitableParam == null && parameter.isInstance(data))
{
mostSuitableParam = parameter;
paramOut[i] = data;
}
if (parameter.isInstance(data) && mostSuitableParam.isAssignableFrom(parameter))
{
mostSuitableParam = parameter;
paramOut = new EventData[params.length];
paramOut[i] = data;
}
}
if (mostSuitableParam != null)
{
try
{
m.invoke(null, (Object[]) paramOut);
}
catch (Exception e)
{
Logger.logException(e);
}
}
}
}
else
{
Logger.log(Severity.ERROR, "There is no event like " + event.getCanonicalName() + " registered.");
}
}
else
{
Logger.log(Severity.ERROR, event.getCanonicalName() + " is not an event!");
}
}
public static void unregisterAll()
{
eventRegistry.clear();
orphans.clear();
}
}

View File

@ -0,0 +1,35 @@
package cz.tefek.tpl;
public class TPJImage
{
int[] data;
int width;
int height;
public TPJImage(int[] pixels, int width, int height)
{
this.data = pixels;
this.width = width;
this.height = height;
}
public int getWidth()
{
return this.width;
}
public int getHeight()
{
return this.height;
}
public int[] getData()
{
return this.data;
}
public int pixelAt(int x, int y)
{
return this.data[x + y * this.width];
}
}

View File

@ -0,0 +1,187 @@
package cz.tefek.tpl;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import javax.imageio.ImageIO;
import cz.tefek.io.asl.resource.ResourceAddress;
import cz.tefek.io.pluto.debug.Logger;
import cz.tefek.io.pluto.debug.SmartSeverity;
/**
* Quick ABGR (8-bit per channel, 32 bits per pixel) texture loader for OpenGL
* use. Color component swizzling may be needed.
*
* @author 493msi
*/
public class TPL
{
private static final int PLACEHOLDER_SIZE = 16;
private static final int PLACEHOLDER_CHECKEDBOARD = 16;
private static final int PLACEHOLDER_CHECKEDBOARD_SQUARE_SIZE = PLACEHOLDER_SIZE / PLACEHOLDER_CHECKEDBOARD;
public static TPNImage load(ResourceAddress file)
{
return file == null ? loadImage(null) : load(file.toPath());
}
public static TPNImage load(String file)
{
if (file == null)
{
return loadImage(null);
}
try
{
return loadImage(ImageIO.read(new File(file)));
}
catch (Exception e)
{
Logger.log(SmartSeverity.ERROR, "[TPL] Image could not be loaded: " + file);
Logger.logException(e);
return loadImage(null);
}
}
public static TPNImage loadImage(BufferedImage image)
{
boolean remake = false;
int width = 0;
int height = 0;
if (image == null)
{
Logger.log(SmartSeverity.WARNING, "[TPL] Null BufferedImage supplied, generating a placeholder.");
remake = true;
}
else
{
width = image.getWidth();
height = image.getHeight();
if (width > 16384 || height > 16384 || width < 1 || height < 1)
{
Logger.log(SmartSeverity.ERROR, "[TPL] BufferedImage size is invalid (< 1 or > 16384), generating a placeholder.");
remake = true;
}
}
if (remake)
{
width = PLACEHOLDER_SIZE;
height = PLACEHOLDER_SIZE;
Logger.log(SmartSeverity.INFO, "[TPL] Generating a substitute image...");
ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 4);
buffer.order(ByteOrder.nativeOrder());
for (int i = 0; i < width * height; i++)
{
int x = i % width;
int y = i / width;
boolean checker = x / PLACEHOLDER_CHECKEDBOARD_SQUARE_SIZE % 2 == y / PLACEHOLDER_CHECKEDBOARD_SQUARE_SIZE % 2;
buffer.put((byte) 0xff); // A
buffer.put((byte) 0x00); // B
buffer.put((byte) 0x00); // G
buffer.put((byte) (checker ? 0xff : 0x00)); // R
}
buffer.flip();
return new TPNImage(buffer, width, height);
}
ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 4).order(ByteOrder.nativeOrder());
BufferedImage copy = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D imgGraphics = copy.createGraphics();
imgGraphics.drawImage(image, 0, copy.getHeight(), copy.getWidth(), 0, 0, 0, image.getWidth(), image.getHeight(), null); // I wonder if this is pixel-perfect
imgGraphics.dispose();
Raster data = copy.getData();
DataBuffer dataBuffer = data.getDataBuffer();
DataBufferByte byteBuffer = (DataBufferByte) dataBuffer;
byte[] byteData = byteBuffer.getData();
buffer.put(byteData);
buffer.flip();
return new TPNImage(buffer, width, height);
}
public static TPJImage loadPixels(String file)
{
TPJImage tImg = null;
BufferedImage image = null;
boolean remake = false;
int width = 0;
int height = 0;
try
{
image = ImageIO.read(new File(file));
width = image.getWidth();
height = image.getHeight();
}
catch (Exception e)
{
Logger.log(SmartSeverity.ERROR, "[TPL] Image could not be loaded: " + file);
Logger.logException(e);
remake = true;
}
if (width > 16384 || height > 16384 || width < 1 || height < 1)
{
Logger.log(SmartSeverity.ERROR, "[TPL] Image size is invalid (< 1 or > 16384): " + file);
Logger.log(SmartSeverity.ERROR, "[TPL] A replacement will be generated.");
remake = true;
}
if (remake)
{
width = PLACEHOLDER_SIZE;
height = PLACEHOLDER_SIZE;
tImg = new TPJImage(new int[width * height], width, height);
Logger.log(SmartSeverity.INFO, "[TPL] Generating a substitute image...");
for (int i = 0; i < width * height; i++)
{
int x = i % width;
int y = i / width;
boolean checker = x / PLACEHOLDER_CHECKEDBOARD_SQUARE_SIZE % 2 == y / PLACEHOLDER_CHECKEDBOARD_SQUARE_SIZE % 2;
tImg.data[i] = checker ? 0xffff0000 : 0xff000000;
}
return tImg;
}
tImg = new TPJImage(new int[width * height], width, height);
for (int i = 0; i < width * height; i++)
{
int pixel = image.getRGB(i % width, i / width);
tImg.data[i] = pixel;
}
return tImg;
}
}

View File

@ -0,0 +1,32 @@
package cz.tefek.tpl;
import java.nio.ByteBuffer;
public class TPNImage
{
ByteBuffer data;
int width;
int height;
public TPNImage(ByteBuffer bfr, int width, int height)
{
this.data = bfr;
this.width = width;
this.height = height;
}
public int getWidth()
{
return this.width;
}
public int getHeight()
{
return this.height;
}
public ByteBuffer getData()
{
return this.data;
}
}

20
plutomesher/pom.xml Normal file
View File

@ -0,0 +1,20 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cz.tefek</groupId>
<artifactId>plutomesher</artifactId>
<version>0.2</version>
<name>plutomesher</name>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>cz.tefek</groupId>
<artifactId>plutostatic</artifactId>
<version>0.3</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,41 @@
package cz.tefek.pluto.engine.graphics.gl;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GL40;
import cz.tefek.pluto.engine.gl.IOpenGLEnum;
public enum DrawMode implements IOpenGLEnum
{
POINTS(GL33.GL_POINTS),
LINES(GL33.GL_LINES),
LINE_LOOP(GL33.GL_LINE_LOOP),
LINE_STRIP(GL33.GL_LINE_STRIP),
TRIANGLES(GL33.GL_TRIANGLES),
TRIANGLE_STRIP(GL33.GL_TRIANGLE_STRIP),
TRIANGLE_FAN(GL33.GL_TRIANGLE_FAN),
@Deprecated
QUADS(GL33.GL_QUADS),
@Deprecated
QUAD_STRIP(GL33.GL_QUAD_STRIP),
@Deprecated
POLYGON(GL33.GL_POLYGON),
LINES_ADJACENCY(GL33.GL_LINES_ADJACENCY),
LINE_STRIP_ADJACENCY(GL33.GL_LINE_STRIP_ADJACENCY),
TRIANGLES_ADJACENCY(GL33.GL_TRIANGLES_ADJACENCY),
TRIANGLE_STRIP_ADJACENCY(GL33.GL_TRIANGLE_STRIP_ADJACENCY),
PATCHES(GL40.GL_PATCHES);
private int glID;
private DrawMode(int id)
{
this.glID = id;
}
@Override
public int getGLID()
{
return this.glID;
}
}

View File

@ -0,0 +1,57 @@
package cz.tefek.pluto.engine.graphics.gl.vao;
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray;
/**
* @author 493msi
*
*/
public class QuadPresets
{
public static VertexArray basicQuad()
{
float[] uvs = { 0, 0, 1, 0, 1, 1, 0, 1 };
float[] positions = { -1, 1, 1, 1, 1, -1, -1, -1 };
int[] indices = { 0, 1, 2, 0, 2, 3 };
VertexArrayBuilder vab = new VertexArrayBuilder();
vab.vertices(new VecArray<>(positions, 2));
vab.uvs(new VecArray<>(uvs, 2));
vab.indices(new VecArray<>(indices, 1));
return vab.export();
}
public static VertexArray halvedSize()
{
float[] uvs = { 0, 0, 1, 0, 1, 1, 0, 1 };
float[] positions = { -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f, -0.5f, -0.5f };
int[] indices = { 0, 1, 2, 0, 2, 3 };
VertexArrayBuilder vab = new VertexArrayBuilder();
vab.vertices(new VecArray<>(positions, 2));
vab.uvs(new VecArray<>(uvs, 2));
vab.indices(new VecArray<>(indices, 1));
return vab.export();
}
public static VertexArray basicNoNeg()
{
float[] uvs = { 0, 0, 1, 0, 1, 1, 0, 1 };
float[] positions = { 0, 1, 1, 1, 1, 0, 0, 0 };
int[] indices = { 0, 1, 2, 0, 2, 3 };
VertexArrayBuilder vab = new VertexArrayBuilder();
vab.vertices(new VecArray<>(positions, 2));
vab.uvs(new VecArray<>(uvs, 2));
vab.indices(new VecArray<>(indices, 1));
return vab.export();
}
}

View File

@ -0,0 +1,145 @@
package cz.tefek.pluto.engine.graphics.gl.vao;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import org.lwjgl.opengl.GL33;
import org.lwjgl.system.MemoryUtil;
import cz.tefek.io.pluto.debug.Logger;
import cz.tefek.io.pluto.debug.SmartSeverity;
import cz.tefek.pluto.engine.graphics.gl.DrawMode;
import cz.tefek.pluto.engine.graphics.gl.vbo.ArrayBuffer;
import cz.tefek.pluto.engine.graphics.gl.vbo.IndexArrayBuffer;
public class VertexArray
{
protected final List<Integer> usedAttribs;
protected final Vector<ArrayBuffer<?>> vertexAttribs;
protected IndexArrayBuffer indices;
private int vertexCount;
protected int glID = 0;
public VertexArray()
{
int maxAttribs = GL33.glGetInteger(GL33.GL_MAX_VERTEX_ATTRIBS);
this.usedAttribs = new ArrayList<>(maxAttribs);
this.vertexAttribs = new Vector<ArrayBuffer<?>>(maxAttribs);
this.vertexAttribs.setSize(maxAttribs);
this.glID = GL33.glGenVertexArrays();
Logger.logf(SmartSeverity.ADDED, "Vertex array ID %d created...\n", this.glID);
}
public void createArrayAttrib(ArrayBuffer<?> buffer, int attribID)
{
this.bind();
buffer.bind();
GL33.glVertexAttribPointer(attribID, buffer.getVertexDimensions(), buffer.getType().getGLID(), false, 0, 0);
this.vertexAttribs.set(attribID, buffer);
this.usedAttribs.add(attribID);
if (!this.hasIndices())
{
this.vertexCount = buffer.getVertexCount();
}
}
public List<ArrayBuffer<?>> getVertexAttribs()
{
return Collections.unmodifiableList(this.vertexAttribs);
}
public int getVertexCount()
{
return this.vertexCount;
}
public void enableAllAttributes()
{
this.usedAttribs.stream().forEach(GL33::glEnableVertexAttribArray);
}
public void bindIndices(IndexArrayBuffer buffer)
{
this.bind();
buffer.bind();
this.indices = buffer;
this.vertexCount = buffer.getVertexCount();
}
public void bind()
{
GL33.glBindVertexArray(this.glID);
}
public void unbind()
{
GL33.glBindVertexArray(0);
}
public void draw(DrawMode mode)
{
if (this.hasIndices())
{
GL33.glDrawElements(mode.getGLID(), this.vertexCount, this.indices.getType().getGLID(), MemoryUtil.NULL);
}
else
{
GL33.glDrawArrays(mode.getGLID(), 0, this.vertexCount);
}
}
public void drawInstanced(DrawMode mode, int count)
{
if (this.hasIndices())
{
GL33.glDrawElementsInstanced(mode.getGLID(), this.vertexCount, this.indices.getType().getGLID(), MemoryUtil.NULL, count);
}
else
{
GL33.glDrawArraysInstanced(mode.getGLID(), 0, this.vertexCount, count);
}
}
public IndexArrayBuffer getIndices()
{
return this.indices;
}
public boolean hasIndices()
{
return this.indices != null;
}
public void delete()
{
this.usedAttribs.stream().map(this.vertexAttribs::get).forEach(ArrayBuffer::delete);
this.vertexAttribs.clear();
this.usedAttribs.clear();
if (this.indices != null)
{
this.indices.delete();
this.indices = null;
}
Logger.logf(SmartSeverity.REMOVED, "Vertex array ID %d deleted...\n", this.glID);
GL33.glDeleteVertexArrays(this.glID);
this.glID = 0;
}
public int getID()
{
return this.glID;
}
}

View File

@ -0,0 +1,42 @@
package cz.tefek.pluto.engine.graphics.gl.vao;
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.ReservedAttributes;
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray;
import cz.tefek.pluto.engine.graphics.gl.vbo.FloatArrayBuffer;
import cz.tefek.pluto.engine.graphics.gl.vbo.IndexArrayBuffer;
public class VertexArrayBuilder
{
protected VertexArray va;
public VertexArrayBuilder()
{
this.va = new VertexArray();
}
public VertexArrayBuilder vertices(VecArray<float[]> vertices)
{
this.va.createArrayAttrib(new FloatArrayBuffer(vertices), ReservedAttributes.POSITION);
return this;
}
public VertexArrayBuilder uvs(VecArray<float[]> uvs)
{
this.va.createArrayAttrib(new FloatArrayBuffer(uvs), ReservedAttributes.UV);
return this;
}
public VertexArrayBuilder indices(VecArray<int[]> indices)
{
this.va.bindIndices(new IndexArrayBuffer(indices));
return this;
}
public VertexArray export()
{
return this.va;
}
}

View File

@ -0,0 +1,9 @@
package cz.tefek.pluto.engine.graphics.gl.vao.attrib;
public class ReservedAttributes
{
public static final int POSITION = 0;
public static final int UV = 1;
public static final int NORMAL = 2;
public static final int COLOR = 3;
}

Some files were not shown because too many files have changed in this diff Show More