Initial unified commit
This commit is contained in:
commit
5ffbee26f7
|
@ -0,0 +1,5 @@
|
|||
/*/target/
|
||||
/*/.settings
|
||||
/*/.project
|
||||
/*/.classpath
|
||||
/.project
|
|
@ -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.
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package cz.tefek.pluto.command.resolver;
|
||||
|
||||
public abstract class AbstractResolver
|
||||
{
|
||||
public abstract Class<?> getOutputType();
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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(""));
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
/testdb
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package cz.tefek.pluto.engine.gui;
|
||||
|
||||
public interface IGUIPipeline
|
||||
{
|
||||
void flush();
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package cz.tefek.pluto.engine.gui;
|
||||
|
||||
public interface IGUIRenderer
|
||||
{
|
||||
void flush();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
|
@ -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<String></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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package cz.tefek.io.modloader;
|
||||
|
||||
public enum ModLoadingPhase
|
||||
{
|
||||
UPACKING,
|
||||
PREPARING,
|
||||
INITIALIZING,
|
||||
WAITING,
|
||||
PRELOADING,
|
||||
LOADING,
|
||||
POSTLOADING,
|
||||
FINISHING,
|
||||
CANCELED,
|
||||
INSTANTIATING,
|
||||
CLASSLOADING,
|
||||
UNLOADING;
|
||||
}
|
|
@ -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
|
||||
{
|
||||
|
||||
}
|
|
@ -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
|
||||
{
|
||||
}
|
|
@ -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
|
||||
{
|
||||
|
||||
}
|
|
@ -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>();
|
||||
}
|
|
@ -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
|
||||
{
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -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
|
||||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package cz.tefek.io.pluto.debug;
|
||||
|
||||
public interface ISeverity
|
||||
{
|
||||
String getDisplayName();
|
||||
|
||||
boolean isStdErr();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package cz.tefek.pluto.eventsystem;
|
||||
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public class EventData
|
||||
{
|
||||
|
||||
}
|
|
@ -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<>();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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 @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();
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue