Version bump, preparation for the layer subsystem implementation

This commit is contained in:
Natty 2022-04-16 14:40:59 +02:00
parent 7daa8c00c7
commit 0e4ec26b62
No known key found for this signature in database
GPG Key ID: 40AB22FA416C7019
34 changed files with 435 additions and 341 deletions

View File

@ -1,33 +1,22 @@
## Features targeted for 22.1.0.0-alpha.0
* `[PlutoGUI]` Initial implementation of the new font renderer
* Full rewrite
* High quality font rendering
* Subpixel rendering support [?]
* Possibly a new system for bitmap fonts
* Improve upon the support of thread-local Pluto instances
* The long term goal is to allow an unlimited amount of Pluto applications at any given time
## Features targeted for 22.2.0.0-alpha.0 ## Features targeted for 22.2.0.0-alpha.0
* The stage subsystem * The layer subsystem
* A "stage", in this context, is a set of assets bound together * A "layer", in this context, is a set of assets bound together
by programming logic, not necessarily a game level. by programming logic.
Stage switching and asset management are handled by the engine. Layer switching and asset management are handled by the engine.
* Upon stage switch * Layers can be stacked on top of each other and run sequentially
from bottom to top
* Upon layer switch
1. Unload unused assets 1. Unload unused assets
2. Load new assets 2. Load new assets
* Provide multiple means of stage switching * Provide multiple means of layer switching
* Three modes with the initial release * Two modes with the initial release, asynchronous switching will come at a later date
1. Instant switch 1. Instant switch
* Assets will be loaded and unloaded synchronously * Assets will be loaded and unloaded synchronously
* The stage switch will happen in one frame * The layer switch will happen in one frame
2. Deferred switch 2. Deferred switch
* The stage will continue running until all assets load * The layer will continue running until all assets load
* Assets will load synchronously, but at a slower pace * Assets will load synchronously, but at a slower pace
to avoid frame stutter to avoid frame stutter
3. Asynchronous switch
* Assets will be loaded in asynchronously, where applicable
* Falls back to deferred switching for synchronous loading,
such as OpenGL texture upload
* Automated asset loading * Automated asset loading
* All asset management will eventually be handled by `PlutoCore` * All asset management will eventually be handled by `PlutoCore`
* This includes audio clips, textures, sprites * This includes audio clips, textures, sprites
@ -38,4 +27,24 @@
* The stage manager should be relatively low-overhead and allow multiple * The stage manager should be relatively low-overhead and allow multiple
instances instances
* Allow stages to be inherited from, creating a stack-like structure * Allow stages to be inherited from, creating a stack-like structure
* `[PlutoAudio]` Integrate the Audio API with the Stage API * `[PlutoAudio]` Integrate the Audio API with the Stage API
## Features targeted for an unspecified future release
* `[PlutoSpritesheet]` Expanded capabilities
* Support for 9-slice rendering
* Support for animated sprite rendering
* Support for multidirectional sprite rendering
* A spritesheet skeleton editor
* `[PlutoRuntime]`
* Asynchronous switch
* Assets will be loaded in asynchronously, where applicable
* Falls back to deferred switching for synchronous loading,
such as OpenGL texture upload
* `[PlutoGUI]` A fully-fledged GUI engine
* Improve font-rendering capabilities
* Subpixel rendering support [?]
* Reimplement support for bitmap fonts
* Improve upon the support of thread-local Pluto instances
* The long term goal is to allow an unlimited amount of Pluto applications at any given time

View File

@ -50,8 +50,7 @@ See `NEXT_RELEASE_DRAFT.md` for details.
### Very high priority ### Very high priority
[ *Implemented in the current release.* ] [ *Implemented in the current release.* ]
* Improve image loading capabilities, possibly rewrite PlutoLib#TPL * Improve image loading capabilities, possibly rewrite PlutoLib#TPL
* The stage system and automated asset loading
### High priority ### High priority
[ *Implemented in the next release.* ] [ *Implemented in the next release.* ]
* Finish PlutoAudio * Finish PlutoAudio

View File

@ -1,3 +1,15 @@
## 22.2.0.0-alpha.0
* `[PlutoRuntime,PlutoCore]` **Initial implementation of the layer system (formerly known as "stage")**
* `[PlutoComponent]` **Added support for dependencies and strengtened type checks**
* `[PlutoComponent]` *Removed* `IComponent` as it was redundant to `AbstractComponent`
* `[Pluto*]` *Removed* JSR 305 annotations in favor of JetBrains annotations
* `[Pluto*]` *Removed* the `ConstantExpression` annotation in favor of `Contract`
* `[PlutoRuntime]` *Moved* `ThreadSensitive` to `org.plutoengine.address`
* `[PlutoAudio]` Transformed into a PlutoLocalComponent
* `[PlutoAudio]` `IAudioStream` is now `AutoCloseable`
* `[PlutoCore]` `InputBus` now tied to a specific `Display` instead of searching for one in the Local
* `[PlutoCore]` Separated `Mouse` and `Keyboard` from `InputBus` into child components
## 22.1.0.0-alpha.1 ## 22.1.0.0-alpha.1
* `plutoengine-demos/basic-application` Made the gradient in the fragment font shader rotatable * `plutoengine-demos/basic-application` Made the gradient in the fragment font shader rotatable

View File

@ -17,13 +17,13 @@ object Versions {
const val steamworks4jServerVersion = "1.8.0" const val steamworks4jServerVersion = "1.8.0"
const val versionYear = 22 const val versionYear = 22
const val versionMajor = 1 const val versionMajor = 2
const val versionMinor = 0 const val versionMinor = 0
const val versionPatch = 0 const val versionPatch = 0
const val isPrerelease = true const val isPrerelease = true
const val prereleaseName = "alpha" const val prereleaseName = "alpha"
const val prerealeaseUpdate = 1 const val prerealeaseUpdate = 0
val versionFull = val versionFull =
if (isPrerelease) if (isPrerelease)

View File

@ -4,6 +4,9 @@ import org.lwjgl.stb.STBVorbis;
import org.lwjgl.stb.STBVorbisInfo; import org.lwjgl.stb.STBVorbisInfo;
import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import org.plutoengine.buffer.BufferHelper;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -12,10 +15,6 @@ import java.nio.ShortBuffer;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import org.plutoengine.buffer.BufferHelper;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
public class AudioLoader public class AudioLoader
{ {
/** /**

View File

@ -2,7 +2,7 @@ package org.plutoengine.audio;
import java.nio.ShortBuffer; import java.nio.ShortBuffer;
public interface IAudioStream public interface IAudioStream extends AutoCloseable
{ {
int getSamples(ShortBuffer pcm); int getSamples(ShortBuffer pcm);

View File

@ -4,10 +4,11 @@ import org.joml.Vector3f;
import org.lwjgl.openal.*; import org.lwjgl.openal.*;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import org.plutoengine.Pluto; import org.plutoengine.Pluto;
import org.plutoengine.component.ComponentToken;
import org.plutoengine.component.PlutoLocalComponent;
import org.plutoengine.logger.Logger; import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity; import org.plutoengine.logger.SmartSeverity;
import javax.annotation.concurrent.ThreadSafe;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.IntBuffer; import java.nio.IntBuffer;
@ -15,16 +16,21 @@ import java.nio.IntBuffer;
* @author 493msi * @author 493msi
* *
*/ */
@ThreadSafe public class AudioEngine extends PlutoLocalComponent
public class AudioEngine
{ {
private static final ThreadLocal<Long> device = ThreadLocal.withInitial(() -> MemoryUtil.NULL); private long device = MemoryUtil.NULL;
private long context = MemoryUtil.NULL;
private ALCapabilities capabilities;
private static final ThreadLocal<Long> context = ThreadLocal.withInitial(() -> MemoryUtil.NULL); public static final ComponentToken<AudioEngine> TOKEN = ComponentToken.create(AudioEngine::new);
private static final ThreadLocal<ALCapabilities> capabilities = new ThreadLocal<>(); private AudioEngine()
{
public static void initialize() }
@Override
protected void onMount(ComponentDependencyManager manager)
{ {
var devicePtr = ALC10.alcOpenDevice((ByteBuffer) null); var devicePtr = ALC10.alcOpenDevice((ByteBuffer) null);
if (devicePtr == MemoryUtil.NULL) if (devicePtr == MemoryUtil.NULL)
@ -35,7 +41,7 @@ public class AudioEngine
return; return;
} }
device.set(devicePtr); this.device = devicePtr;
var contextPtr = ALC10.alcCreateContext(devicePtr, (IntBuffer) null); var contextPtr = ALC10.alcCreateContext(devicePtr, (IntBuffer) null);
if (contextPtr == MemoryUtil.NULL) if (contextPtr == MemoryUtil.NULL)
@ -48,7 +54,7 @@ public class AudioEngine
return; return;
} }
context.set(contextPtr); this.context = contextPtr;
EXTThreadLocalContext.alcSetThreadContext(contextPtr); EXTThreadLocalContext.alcSetThreadContext(contextPtr);
@ -61,25 +67,27 @@ public class AudioEngine
Logger.logf(SmartSeverity.AUDIO, "OpenAL11: %b\n", alCapabilities.OpenAL11); Logger.logf(SmartSeverity.AUDIO, "OpenAL11: %b\n", alCapabilities.OpenAL11);
} }
capabilities.set(alCapabilities); this.capabilities = alCapabilities;
Logger.log(SmartSeverity.AUDIO_PLUS, "Audio engine started.");
} }
public static void setSpeed(Vector3f speed) public void setSpeed(Vector3f speed)
{ {
AL10.alListener3f(AL10.AL_VELOCITY, speed.x, speed.y, speed.z); AL10.alListener3f(AL10.AL_VELOCITY, speed.x, speed.y, speed.z);
} }
public static void setPosition(Vector3f position) public void setPosition(Vector3f position)
{ {
AL10.alListener3f(AL10.AL_POSITION, position.x, position.y, position.z); AL10.alListener3f(AL10.AL_POSITION, position.x, position.y, position.z);
} }
public static void setVolume(float volume) public void setVolume(float volume)
{ {
AL10.alListenerf(AL10.AL_GAIN, volume); AL10.alListenerf(AL10.AL_GAIN, volume);
} }
public static void setOrientation(Vector3f at, Vector3f up) public void setOrientation(Vector3f at, Vector3f up)
{ {
float[] data = new float[6]; float[] data = new float[6];
data[0] = at.x; data[0] = at.x;
@ -91,18 +99,29 @@ public class AudioEngine
AL10.alListenerfv(AL10.AL_ORIENTATION, data); AL10.alListenerfv(AL10.AL_ORIENTATION, data);
} }
public static boolean isReady() public boolean isReady()
{ {
return capabilities.get() != null; return this.capabilities != null;
} }
public static void exit() @Override
protected void onUnmount()
{ {
EXTThreadLocalContext.alcSetThreadContext(MemoryUtil.NULL); Logger.log(SmartSeverity.AUDIO_MINUS, "Shutting down the audio engine.");
ALC10.alcDestroyContext(context.get());
ALC10.alcCloseDevice(device.get());
context.remove(); EXTThreadLocalContext.alcSetThreadContext(MemoryUtil.NULL);
device.remove();
ALC10.alcDestroyContext(this.context);
ALC10.alcCloseDevice(this.device);
this.context = MemoryUtil.NULL;
this.device = MemoryUtil.NULL;
this.capabilities = null;
}
@Override
public boolean isUnique()
{
return true;
} }
} }

View File

@ -3,12 +3,11 @@ package org.plutoengine.audio.al;
import org.lwjgl.openal.AL10; import org.lwjgl.openal.AL10;
import org.lwjgl.openal.SOFTDirectChannels; import org.lwjgl.openal.SOFTDirectChannels;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import org.plutoengine.audio.IAudioStream;
import org.plutoengine.audio.ISeekableAudioTrack;
import java.nio.ShortBuffer; import java.nio.ShortBuffer;
import org.plutoengine.audio.ISeekableAudioTrack;
import org.plutoengine.audio.IAudioStream;
public class AudioTrack extends AudioSource public class AudioTrack extends AudioSource
{ {
private static final int BUFFER_SIZE_PER_CHANNEL = 16384; private static final int BUFFER_SIZE_PER_CHANNEL = 16384;
@ -56,10 +55,8 @@ public class AudioTrack extends AudioSource
public boolean play() public boolean play()
{ {
if (this.track instanceof ISeekableAudioTrack) if (this.track instanceof ISeekableAudioTrack seekableAudioTrack)
{ seekableAudioTrack.rewind();
((ISeekableAudioTrack) this.track).rewind();
}
for (int buf : this.buffers) for (int buf : this.buffers)
{ {
@ -82,9 +79,7 @@ public class AudioTrack extends AudioSource
int samplesPerChannel = this.track.getSamples(this.pcm); int samplesPerChannel = this.track.getSamples(this.pcm);
if (samplesPerChannel == 0) if (samplesPerChannel == 0)
{
return; return;
}
var samples = samplesPerChannel * this.track.getChannels(); var samples = samplesPerChannel * this.track.getChannels();
this.pcm.limit(samples); this.pcm.limit(samples);

View File

@ -6,8 +6,9 @@ plugins {
description = "A module acting as glue for all PlutoEngine components." description = "A module acting as glue for all PlutoEngine components."
dependencies { dependencies {
api("org.apache.commons:commons-lang3:3.12.0") api("org.jetbrains", "annotations", "23.0.0")
api("org.apache.commons:commons-collections4:4.4")
implementation("org.apache.commons", "commons-lang3", "3.12.0")
implementation("org.apache.commons", "commons-collections4", "4.4")
api("com.google.code.findbugs:jsr305:3.0.2")
} }

View File

@ -1,33 +1,61 @@
package org.plutoengine.component; package org.plutoengine.component;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
public abstract class AbstractComponent implements IComponent public abstract class AbstractComponent<T extends AbstractComponent<? super T>>
{ {
private static final AtomicLong ID_SOURCE = new AtomicLong(); private static final AtomicLong ID_SOURCE = new AtomicLong();
private final long id; private final long id;
private ComponentDependencyManager manager;
protected AbstractComponent() protected AbstractComponent()
{ {
this.id = ID_SOURCE.getAndIncrement(); this.id = ID_SOURCE.getAndIncrement();
} }
@Override
public long getID() public long getID()
{ {
return this.id; return this.id;
} }
@Override /**
public void onMount() throws Exception * Denotes whether this component should be unique.
* Unique components can only exist once per instance
* in a given {@link ComponentManager}.
*
* @return whether this component should be unique
*/
public abstract boolean isUnique();
void initialize(ComponentManager<? super T> manager) throws Exception
{
this.manager = this.new ComponentDependencyManager(manager);
onMount(this.manager);
}
protected void onMount(ComponentDependencyManager manager) throws Exception
{ {
} }
@Override void destroy(ComponentManager<? super T> manager) throws Exception
public void onUnmount() throws Exception {
if (this.manager.dependencies != null)
{
this.manager.dependencies.forEach(manager::removeComponent);
this.manager.dependencies.clear();
}
this.onUnmount();
}
protected void onUnmount() throws Exception
{ {
} }
@ -37,7 +65,7 @@ public abstract class AbstractComponent implements IComponent
{ {
if (this == o) return true; if (this == o) return true;
if (o == null || this.getClass() != o.getClass()) return false; if (o == null || this.getClass() != o.getClass()) return false;
AbstractComponent that = (AbstractComponent) o; AbstractComponent<?> that = (AbstractComponent<?>) o;
return this.id == that.id; return this.id == that.id;
} }
@ -46,4 +74,27 @@ public abstract class AbstractComponent implements IComponent
{ {
return Objects.hash(this.id); return Objects.hash(this.id);
} }
public class ComponentDependencyManager
{
private final ComponentManager<? super T> manager;
private Deque<T> dependencies;
private ComponentDependencyManager(ComponentManager<? super T> componentManager)
{
this.manager = componentManager;
}
public <R extends T> R declareDependency(ComponentToken<R> token)
{
if (this.dependencies == null)
this.dependencies = new ArrayDeque<>();
var dependency = this.manager.addComponent(token);
this.dependencies.push(dependency);
return dependency;
}
}
} }

View File

@ -4,21 +4,21 @@ import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
import org.apache.commons.collections4.multimap.HashSetValuedHashMap; import org.apache.commons.collections4.multimap.HashSetValuedHashMap;
import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.ClassUtils;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nonnull;
import java.util.*; import java.util.*;
import java.util.stream.Stream; import java.util.stream.Stream;
public class ComponentManager<R extends AbstractComponent> public class ComponentManager<R extends AbstractComponent<R>>
{ {
private final Class<R> base; private final Class<R> base;
protected final MultiValuedMap<ComponentToken<? extends R>, R> components; protected final MultiValuedMap<ComponentToken<?>, R> components;
protected final Map<R, ComponentToken<? extends R>> tokens; protected final Map<R, ComponentToken<?>> tokens;
protected final MultiValuedMap<Class<?>, R> implementationProviders; protected final MultiValuedMap<Class<?>, R> implementationProviders;
protected final MultiValuedMap<R, Class<?>> implementationReceivers; protected final MultiValuedMap<R, Class<?>> implementationReceivers;
public ComponentManager(@Nonnull Class<R> base) public ComponentManager(@NotNull Class<R> base)
{ {
this.base = base; this.base = base;
this.components = new HashSetValuedHashMap<>(); this.components = new HashSetValuedHashMap<>();
@ -27,7 +27,7 @@ public class ComponentManager<R extends AbstractComponent>
this.implementationReceivers = new ArrayListValuedHashMap<>(); this.implementationReceivers = new ArrayListValuedHashMap<>();
} }
public <T extends R> T addComponent(@Nonnull ComponentToken<T> token) public <T extends R> T addComponent(@NotNull ComponentToken<T> token)
{ {
T component = token.createInstance(); T component = token.createInstance();
var clazz = component.getClass(); var clazz = component.getClass();
@ -52,7 +52,7 @@ public class ComponentManager<R extends AbstractComponent>
try try
{ {
component.onMount(); component.initialize(this);
} }
catch (Exception e) catch (Exception e)
{ {
@ -67,33 +67,33 @@ public class ComponentManager<R extends AbstractComponent>
return this.base; return this.base;
} }
public <T extends R> Stream<T> streamComponents(@Nonnull Class<T> componentClazz) public <T extends R> Stream<T> streamComponents(@NotNull Class<T> componentClazz)
{ {
var providers = this.implementationProviders.get(componentClazz); var providers = this.implementationProviders.get(componentClazz);
return providers.stream().map(componentClazz::cast); return providers.stream().map(componentClazz::cast);
} }
public <T extends R> T getComponent(@Nonnull Class<T> componentClazz) throws NoSuchElementException public <T extends R> T getComponent(@NotNull Class<T> componentClazz) throws NoSuchElementException
{ {
return this.streamComponents(componentClazz) return this.streamComponents(componentClazz)
.findAny() .findAny()
.orElseThrow(); .orElseThrow();
} }
public <T extends R> T getComponent(@Nonnull Class<T> componentClazz, @Nonnull Comparator<T> heuristic) throws NoSuchElementException public <T extends R> T getComponent(@NotNull Class<T> componentClazz, @NotNull Comparator<T> heuristic) throws NoSuchElementException
{ {
return this.streamComponents(componentClazz) return this.streamComponents(componentClazz)
.max(heuristic) .max(heuristic)
.orElseThrow(); .orElseThrow();
} }
public <T extends R> List<T> getComponents(@Nonnull Class<T> componentClazz) public <T extends R> List<T> getComponents(@NotNull Class<T> componentClazz)
{ {
return this.streamComponents(componentClazz).toList(); return this.streamComponents(componentClazz).toList();
} }
public void removeComponent(@Nonnull R component) throws IllegalArgumentException public void removeComponent(@NotNull R component) throws IllegalArgumentException
{ {
var token = this.tokens.remove(component); var token = this.tokens.remove(component);
@ -108,7 +108,7 @@ public class ComponentManager<R extends AbstractComponent>
try try
{ {
component.onUnmount(); component.destroy(this);
} }
catch (Exception e) catch (Exception e)
{ {
@ -116,7 +116,7 @@ public class ComponentManager<R extends AbstractComponent>
} }
} }
public <T extends R> void removeComponents(@Nonnull ComponentToken<T> componentToken) public <T extends R> void removeComponents(@NotNull ComponentToken<T> componentToken)
{ {
var activeComponents = this.components.remove(componentToken); var activeComponents = this.components.remove(componentToken);

View File

@ -1,11 +1,12 @@
package org.plutoengine.component; package org.plutoengine.component;
import javax.annotation.Nonnull; import org.jetbrains.annotations.NotNull;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier; import java.util.function.Supplier;
public final class ComponentToken<T extends IComponent> public final class ComponentToken<T extends AbstractComponent<? super T>>
{ {
private static final AtomicLong ID_SOURCE = new AtomicLong(); private static final AtomicLong ID_SOURCE = new AtomicLong();
@ -18,7 +19,7 @@ public final class ComponentToken<T extends IComponent>
this.supplier = valueSupplier; this.supplier = valueSupplier;
} }
public static <T extends IComponent> ComponentToken<T> create(@Nonnull Supplier<T> valueSupplier) public static <G extends AbstractComponent<? super G>> ComponentToken<G> create(@NotNull Supplier<G> valueSupplier)
{ {
return new ComponentToken<>(valueSupplier); return new ComponentToken<>(valueSupplier);
} }

View File

@ -1,22 +0,0 @@
package org.plutoengine.component;
public interface IComponent
{
/**
* Denotes whether this component should be unique.
* Unique components can only exist once per instance
* in a given {@link ComponentManager}.
*
* @return whether this component should be unique
*
* @author 493msi
* @since 20.2.0.0-alpha.3
*/
boolean isUnique();
long getID();
void onMount() throws Exception;
void onUnmount() throws Exception;
}

View File

@ -243,9 +243,8 @@ public abstract class PlutoApplication
this.display.createOpenGLCapabilities(); this.display.createOpenGLCapabilities();
components.addComponent(InputBus.TOKEN); var inputBus = components.addComponent(InputBus.fromDisplay(this.display));
var audioEngine = components.addComponent(AudioEngine.TOKEN);
AudioEngine.initialize();
var modLoader = components.addComponent(ModLoader.TOKEN); var modLoader = components.addComponent(ModLoader.TOKEN);
@ -259,7 +258,6 @@ public abstract class PlutoApplication
this.display.setIcons(icons); this.display.setIcons(icons);
} }
while (!this.display.isClosing()) while (!this.display.isClosing())
{ {
GL33.glViewport(0, 0, this.display.getWidth(), this.display.getHeight()); GL33.glViewport(0, 0, this.display.getWidth(), this.display.getHeight());
@ -270,20 +268,19 @@ public abstract class PlutoApplication
this.display.swapBuffers(); this.display.swapBuffers();
InputBus.resetStates(); inputBus.resetStates();
this.display.pollEvents(); this.display.pollEvents();
} }
AudioEngine.exit(); components.removeComponent(audioEngine);
modLoader.unload(); modLoader.unload();
GL.destroy(); GL.destroy();
components.removeComponent(modLoader); components.removeComponent(modLoader);
components.removeComponent(inputBus);
components.removeComponents(InputBus.TOKEN);
this.display.destroy(); this.display.destroy();

View File

@ -1,100 +1,37 @@
package org.plutoengine.input; package org.plutoengine.input;
import org.lwjgl.glfw.GLFW;
import org.plutoengine.PlutoLocal;
import org.plutoengine.address.ThreadSensitive;
import org.plutoengine.component.ComponentToken; import org.plutoengine.component.ComponentToken;
import org.plutoengine.component.PlutoLocalComponent; import org.plutoengine.component.PlutoLocalComponent;
import org.plutoengine.display.Display; import org.plutoengine.display.Display;
@ThreadSensitive(localContexts = true)
public class InputBus extends PlutoLocalComponent public class InputBus extends PlutoLocalComponent
{ {
public static final ComponentToken<InputBus> TOKEN = ComponentToken.create(InputBus::new); public static ComponentToken<InputBus> fromDisplay(Display display)
private final KeyboardInputCallback keyboard = new KeyboardInputCallback();
private final MouseButtonCallback mouseButton = new MouseButtonCallback();
private final CursorPositionCallback cursorPosition = new CursorPositionCallback();
private final ScrollInputCallback scroll = new ScrollInputCallback();
private final KeyboardCharInput charInput = new KeyboardCharInput();
private long windowPointer;
private InputBus()
{ {
return ComponentToken.create(() -> new InputBus(display));
} }
private static InputBus instance() private final Display display;
private Keyboard keyboard;
private Mouse mouse;
private InputBus(Display display)
{ {
return PlutoLocal.components().getComponent(InputBus.class); this.display = display;
} }
@Override @Override
public void onMount() protected void onMount(ComponentDependencyManager manager)
{ {
var display = PlutoLocal.components().getComponent(Display.class); this.keyboard = manager.declareDependency(ComponentToken.create(() -> new Keyboard(this.display.getWindowPointer())));
this.windowPointer = display.getWindowPointer(); this.mouse = manager.declareDependency(ComponentToken.create(() -> new Mouse(this.display.getWindowPointer())));
GLFW.glfwSetKeyCallback(this.windowPointer, this.keyboard);
GLFW.glfwSetMouseButtonCallback(this.windowPointer, this.mouseButton);
GLFW.glfwSetCursorPosCallback(this.windowPointer, this.cursorPosition);
GLFW.glfwSetScrollCallback(this.windowPointer, this.scroll);
GLFW.glfwSetCharCallback(this.windowPointer, this.charInput);
} }
public void resetStates()
@Override
public void onUnmount()
{ {
GLFW.glfwSetKeyCallback(this.windowPointer, null); this.keyboard.resetStates();
GLFW.glfwSetMouseButtonCallback(this.windowPointer, null); this.mouse.resetStates();
GLFW.glfwSetCursorPosCallback(this.windowPointer, null);
GLFW.glfwSetScrollCallback(this.windowPointer, null);
GLFW.glfwSetCharCallback(this.windowPointer, null);
this.scroll.free();
this.mouseButton.free();
this.keyboard.free();
this.cursorPosition.free();
this.charInput.free();
}
public static KeyboardInputCallback keyboard()
{
return instance().keyboard;
}
public static MouseButtonCallback mouseButtons()
{
return instance().mouseButton;
}
public static ScrollInputCallback scroll()
{
return instance().scroll;
}
public static CursorPositionCallback cursorPosition()
{
return instance().cursorPosition;
}
public static KeyboardCharInput charInput()
{
return instance().charInput;
}
public static void resetStates()
{
var instance = instance();
instance.keyboard.resetPressed();
instance.mouseButton.reset();
instance.scroll.reset();
instance.cursorPosition.reset();
instance.charInput.reset();
} }
@Override @Override
@ -103,81 +40,4 @@ public class InputBus extends PlutoLocalComponent
return true; return true;
} }
@ThreadSensitive(localContexts = true)
public static class Mouse
{
public static boolean clicked(int button)
{
return instance().mouseButton.buttonClicked[button];
}
public static boolean released(int button)
{
return instance().mouseButton.buttonReleased[button];
}
public static boolean isButtonDown(int button)
{
return instance().mouseButton.buttonDown[button];
}
public static double getX()
{
return instance().cursorPosition.getX();
}
public static double getY()
{
return instance().cursorPosition.getY();
}
public static boolean isInside(int x1, int y1, int x2, int y2)
{
return instance().cursorPosition.isInside(x1, y1, x2, y2);
}
public static double getDX()
{
return instance().cursorPosition.getDeltaX();
}
public static double getDY()
{
return instance().cursorPosition.getDeltaY();
}
public static double getScrollX()
{
return instance().scroll.getXScroll();
}
public static double getScrollY()
{
return instance().scroll.getYScroll();
}
}
@ThreadSensitive(localContexts = true)
public static class Keyboard
{
public static boolean pressed(int key)
{
return instance().keyboard.hasBeenPressed(key);
}
public static boolean released(int key)
{
return instance().keyboard.hasBeenReleased(key);
}
public static boolean isKeyDown(int key)
{
return instance().keyboard.isKeyDown(key);
}
public static String getTypedText()
{
return instance().charInput.getTypedText();
}
}
} }

View File

@ -0,0 +1,78 @@
package org.plutoengine.input;
import org.lwjgl.glfw.GLFW;
import org.plutoengine.component.PlutoLocalComponent;
import org.plutoengine.input.callback.KeyboardCharInput;
import org.plutoengine.input.callback.KeyboardInputCallback;
public class Keyboard extends PlutoLocalComponent
{
private final KeyboardInputCallback keyboard = new KeyboardInputCallback();
private final KeyboardCharInput charInput = new KeyboardCharInput();
private final long windowPointer;
Keyboard(long windowPointer)
{
this.windowPointer = windowPointer;
}
public KeyboardInputCallback keyboard()
{
return this.keyboard;
}
public KeyboardCharInput charInput()
{
return this.charInput;
}
void resetStates()
{
this.keyboard.resetPressed();
this.charInput.reset();
}
@Override
protected void onMount(ComponentDependencyManager manager)
{
GLFW.glfwSetKeyCallback(this.windowPointer, this.keyboard);
GLFW.glfwSetCharCallback(this.windowPointer, this.charInput);
}
@Override
protected void onUnmount() throws Exception
{
GLFW.glfwSetKeyCallback(this.windowPointer, null);
GLFW.glfwSetCharCallback(this.windowPointer, null);
this.keyboard.free();
this.charInput.free();
}
public boolean pressed(int key)
{
return this.keyboard.hasBeenPressed(key);
}
public boolean released(int key)
{
return this.keyboard.hasBeenReleased(key);
}
public boolean isKeyDown(int key)
{
return this.keyboard.isKeyDown(key);
}
public String getTypedText()
{
return this.charInput.getTypedText();
}
@Override
public boolean isUnique()
{
return false;
}
}

View File

@ -0,0 +1,119 @@
package org.plutoengine.input;
import org.lwjgl.glfw.GLFW;
import org.plutoengine.component.PlutoLocalComponent;
import org.plutoengine.input.callback.CursorPositionCallback;
import org.plutoengine.input.callback.MouseButtonCallback;
import org.plutoengine.input.callback.ScrollInputCallback;
public class Mouse extends PlutoLocalComponent
{
private final MouseButtonCallback mouseButton = new MouseButtonCallback();
private final CursorPositionCallback cursorPosition = new CursorPositionCallback();
private final ScrollInputCallback scroll = new ScrollInputCallback();
private final long windowPointer;
Mouse(long windowPointer)
{
this.windowPointer = windowPointer;
}
public MouseButtonCallback mouseButtons()
{
return this.mouseButton;
}
public ScrollInputCallback scroll()
{
return this.scroll;
}
public CursorPositionCallback cursorPosition()
{
return this.cursorPosition;
}
void resetStates()
{
this.mouseButton.reset();
this.scroll.reset();
this.cursorPosition.reset();
}
@Override
protected void onMount(ComponentDependencyManager manager)
{
GLFW.glfwSetMouseButtonCallback(this.windowPointer, this.mouseButton);
GLFW.glfwSetCursorPosCallback(this.windowPointer, this.cursorPosition);
GLFW.glfwSetScrollCallback(this.windowPointer, this.scroll);
}
@Override
protected void onUnmount() throws Exception
{
GLFW.glfwSetMouseButtonCallback(this.windowPointer, null);
GLFW.glfwSetCursorPosCallback(this.windowPointer, null);
GLFW.glfwSetScrollCallback(this.windowPointer, null);
this.mouseButton.free();
this.cursorPosition.free();
this.scroll.free();
}
public boolean clicked(int button)
{
return this.mouseButton.buttonClicked[button];
}
public boolean released(int button)
{
return this.mouseButton.buttonReleased[button];
}
public boolean isButtonDown(int button)
{
return this.mouseButton.buttonDown[button];
}
public double getX()
{
return this.cursorPosition.getX();
}
public double getY()
{
return this.cursorPosition.getY();
}
public boolean isInside(int x1, int y1, int x2, int y2)
{
return this.cursorPosition.isInside(x1, y1, x2, y2);
}
public double getDX()
{
return this.cursorPosition.getDeltaX();
}
public double getDY()
{
return this.cursorPosition.getDeltaY();
}
public double getScrollX()
{
return this.scroll.getXScroll();
}
public double getScrollY()
{
return this.scroll.getYScroll();
}
@Override
public boolean isUnique()
{
return false;
}
}

View File

@ -1,4 +1,4 @@
package org.plutoengine.input; package org.plutoengine.input.callback;
import org.lwjgl.glfw.GLFWCursorPosCallback; import org.lwjgl.glfw.GLFWCursorPosCallback;

View File

@ -1,4 +1,4 @@
package org.plutoengine.input; package org.plutoengine.input.callback;
import org.lwjgl.glfw.GLFWCharCallback; import org.lwjgl.glfw.GLFWCharCallback;

View File

@ -1,4 +1,4 @@
package org.plutoengine.input; package org.plutoengine.input.callback;
import org.lwjgl.glfw.GLFWKeyCallback; import org.lwjgl.glfw.GLFWKeyCallback;

View File

@ -1,4 +1,4 @@
package org.plutoengine.input; package org.plutoengine.input.callback;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWMouseButtonCallback; import org.lwjgl.glfw.GLFWMouseButtonCallback;

View File

@ -1,4 +1,4 @@
package org.plutoengine.input; package org.plutoengine.input.callback;
import org.lwjgl.glfw.GLFWScrollCallback; import org.lwjgl.glfw.GLFWScrollCallback;

View File

@ -5,7 +5,7 @@ import org.lwjgl.opengl.*;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import org.plutoengine.Pluto; import org.plutoengine.Pluto;
import org.plutoengine.address.ThreadSensitive; import org.plutoengine.annotation.ThreadSensitive;
import org.plutoengine.component.PlutoLocalComponent; import org.plutoengine.component.PlutoLocalComponent;
import org.plutoengine.gl.GLDebugInfo; import org.plutoengine.gl.GLDebugInfo;
import org.plutoengine.logger.Logger; import org.plutoengine.logger.Logger;

View File

@ -1,6 +1,6 @@
package org.plutoengine; package org.plutoengine;
import org.plutoengine.address.ThreadSensitive; import org.plutoengine.annotation.ThreadSensitive;
import org.plutoengine.component.PlutoLocalComponent; import org.plutoengine.component.PlutoLocalComponent;
import org.plutoengine.component.ComponentManager; import org.plutoengine.component.ComponentManager;

View File

@ -1,8 +1,7 @@
package org.plutoengine; package org.plutoengine;
import org.plutoengine.address.ConstantExpression; import org.jetbrains.annotations.NotNull;
import javax.annotation.Nonnull;
import java.util.Objects; import java.util.Objects;
public final class PlutoVersion implements IVersion<PlutoVersion> public final class PlutoVersion implements IVersion<PlutoVersion>
@ -75,12 +74,11 @@ public final class PlutoVersion implements IVersion<PlutoVersion>
return this.prereleaseNumber; return this.prereleaseNumber;
} }
@ConstantExpression public static PlutoVersion of(@NotNull String str)
public static PlutoVersion of(@Nonnull String str)
{ {
assert !str.isBlank(); assert !str.isBlank();
assert str.matches("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+([+\\-][a-zA-Z0-9]+(\\.[0-9]+)?)?"); assert str.matches("\\d+\\.\\d+\\.\\d+\\.\\d+([+\\-][a-zA-Z\\d]+(\\.\\d+)?)?");
var parts = str.split("[+\\-]"); var parts = str.split("[+\\-]");
@ -141,7 +139,7 @@ public final class PlutoVersion implements IVersion<PlutoVersion>
} }
@Override @Override
public int compareTo(@Nonnull PlutoVersion o) public int compareTo(@NotNull PlutoVersion o)
{ {
int yearDiff = this.year - o.year; int yearDiff = this.year - o.year;
if (yearDiff != 0) if (yearDiff != 0)

View File

@ -1,22 +0,0 @@
package org.plutoengine.address;
import javax.annotation.meta.TypeQualifier;
import java.lang.annotation.*;
/**
* Denotes that the target field or method should be a constant expression - it is final, has no state
* and always yields the same deterministic result for given input. Generally, annotated methods
* should be thread-safe, however this is not required.
*
* @author 493msi
*
* @since 20.2.0.0-alpha.3
* */
@TypeQualifier
@Documented
@Retention(RetentionPolicy.CLASS)
@Target({ ElementType.METHOD, ElementType.FIELD })
public @interface ConstantExpression
{
}

View File

@ -1,9 +1,6 @@
package org.plutoengine.address; package org.plutoengine.annotation;
import java.lang.annotation.ElementType; import java.lang.annotation.*;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** /**
* <p> * <p>
@ -22,6 +19,7 @@ import java.lang.annotation.Target;
*/ */
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@Inherited
public @interface ThreadSensitive public @interface ThreadSensitive
{ {
/** /**

View File

@ -1,5 +1,5 @@
package org.plutoengine.component; package org.plutoengine.component;
public abstract class PlutoGlobalComponent extends AbstractComponent public abstract class PlutoGlobalComponent extends AbstractComponent<PlutoGlobalComponent>
{ {
} }

View File

@ -1,6 +1,9 @@
package org.plutoengine.component; package org.plutoengine.component;
public abstract class PlutoLocalComponent extends AbstractComponent import org.plutoengine.annotation.ThreadSensitive;
@ThreadSensitive(localContexts = true)
public abstract class PlutoLocalComponent extends AbstractComponent<PlutoLocalComponent>
{ {
} }

View File

@ -1,14 +1,14 @@
package org.plutoengine.logger; package org.plutoengine.logger;
import org.plutoengine.component.ComponentToken;
import org.plutoengine.component.PlutoGlobalComponent;
import org.plutoengine.resource.filesystem.ResourceManager;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import org.plutoengine.component.ComponentToken;
import org.plutoengine.component.PlutoGlobalComponent;
import org.plutoengine.resource.filesystem.ResourceManager;
/** /**
* <p> * <p>
* A simple static logger writing to both standard output and a log file. * A simple static logger writing to both standard output and a log file.
@ -48,7 +48,7 @@ public class Logger extends PlutoGlobalComponent
* @since pre-alpha * @since pre-alpha
* */ * */
@Override @Override
public void onMount() throws Exception protected void onMount(ComponentDependencyManager manager) throws Exception
{ {
this.onUnmount(); this.onUnmount();
@ -93,7 +93,7 @@ public class Logger extends PlutoGlobalComponent
* @since pre-alpha * @since pre-alpha
* */ * */
@Override @Override
public void onUnmount() throws Exception protected void onUnmount() throws Exception
{ {
if (fileLog != null) if (fileLog != null)
fileLog.close(); fileLog.close();

View File

@ -1,6 +1,7 @@
package org.plutoengine.logger; package org.plutoengine.logger;
import javax.annotation.Nonnull; import org.jetbrains.annotations.NotNull;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@ -37,14 +38,14 @@ public class OutputSplitStream extends OutputStream
} }
@Override @Override
public void write(@Nonnull byte[] b) throws IOException public void write(byte @NotNull [] b) throws IOException
{ {
this.outputStreamA.write(b); this.outputStreamA.write(b);
this.outputStreamB.write(b); this.outputStreamB.write(b);
} }
@Override @Override
public void write(@Nonnull byte[] b, int off, int len) throws IOException public void write(byte @NotNull [] b, int off, int len) throws IOException
{ {
this.outputStreamA.write(b, off, len); this.outputStreamA.write(b, off, len);
this.outputStreamB.write(b, off, len); this.outputStreamB.write(b, off, len);

View File

@ -3,19 +3,15 @@ package org.plutoengine.resource.filesystem;
import org.plutoengine.address.VirtualAddress; import org.plutoengine.address.VirtualAddress;
import org.plutoengine.mod.Mod; import org.plutoengine.mod.Mod;
import javax.annotation.concurrent.ThreadSafe;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.ProviderNotFoundException; import java.nio.file.ProviderNotFoundException;
import java.nio.file.spi.FileSystemProvider; import java.nio.file.spi.FileSystemProvider;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.ServiceConfigurationError; import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
@ThreadSafe
public class ResourceManager implements Closeable public class ResourceManager implements Closeable
{ {
public static final Path GLOBAL_ROOT = Path.of(""); public static final Path GLOBAL_ROOT = Path.of("");

View File

@ -1,18 +1,20 @@
package org.plutoengine.tpl; package org.plutoengine.tpl;
import javax.annotation.Nullable; import org.jetbrains.annotations.Nullable;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Path;
import org.lwjgl.BufferUtils; import org.lwjgl.BufferUtils;
import org.plutoengine.logger.Logger; import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity; import org.plutoengine.logger.SmartSeverity;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
/** /**
* Quick ABGR (8-bit per channel, 32 bits per pixel) and grayscale image loader for OpenGL textures. * Quick ABGR (8-bit per channel, 32 bits per pixel) and grayscale image loader for OpenGL textures.
* Color component swizzling may be needed. * Color component swizzling may be needed.

View File

@ -12,7 +12,7 @@ import org.plutoengine.graphics.ImmediateFontRenderer;
import org.plutoengine.graphics.PlutoGUIMod; import org.plutoengine.graphics.PlutoGUIMod;
import org.plutoengine.graphics.RectangleRenderer2D; import org.plutoengine.graphics.RectangleRenderer2D;
import org.plutoengine.graphics.gui.FontShader; import org.plutoengine.graphics.gui.FontShader;
import org.plutoengine.input.InputBus; import org.plutoengine.input.Keyboard;
import org.plutoengine.libra.paint.LiGradientPaint; import org.plutoengine.libra.paint.LiGradientPaint;
import org.plutoengine.libra.paint.LiPaint; import org.plutoengine.libra.paint.LiPaint;
import org.plutoengine.libra.text.shaping.TextStyleOptions; import org.plutoengine.libra.text.shaping.TextStyleOptions;
@ -92,7 +92,7 @@ public class Main extends PlutoApplication
welcomeStyle.setPaint(LiPaint.horizontaLinearGradient(stops)); welcomeStyle.setPaint(LiPaint.horizontaLinearGradient(stops));
ImmediateFontRenderer.drawString(0, 100, "Welcome to PlutoEngine v. %s!".formatted(Pluto.VERSION), BasicApplicationDemoMod.font, welcomeStyle); ImmediateFontRenderer.drawString(0, 100, "Welcome to PlutoEngine v. %s!".formatted(Pluto.VERSION), BasicApplicationDemoMod.font, welcomeStyle);
if (InputBus.Keyboard.pressed(GLFW.GLFW_KEY_R)) if (PlutoLocal.components().getComponent(Keyboard.class).pressed(GLFW.GLFW_KEY_R))
{ {
var shader = PlutoGUIMod.fontShader; var shader = PlutoGUIMod.fontShader;