Added periodic task (but unfinished), have to rework a bit AsyncTasks (add a scheduler to add cancellability)

This commit is contained in:
Quentin Legot 2023-02-03 12:31:51 +01:00
parent 5d89e92abd
commit 863952b120
16 changed files with 253 additions and 29 deletions

View File

@ -1,18 +1,8 @@
plugins {
id 'java'
id 'maven-publish'
}
dependencies { dependencies {
implementation 'org.postgresql:postgresql:42.5.0' implementation 'org.postgresql:postgresql:42.5.0'
testImplementation 'com.google.code.gson:gson:2.10' testImplementation 'com.google.code.gson:gson:2.10'
testImplementation "org.junit.jupiter:junit-jupiter-api:5.9.0" testImplementation "org.junit.jupiter:junit-jupiter-api:${project.junit_version}"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.9.0" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${project.junit_version}"
}
java {
withSourcesJar()
withJavadocJar()
} }
test { test {

View File

@ -1,17 +1,43 @@
plugins { plugins {
id 'java' id 'fabric-loom' version '1.0-SNAPSHOT'
id 'maven-publish'
} }
dependencies { dependencies {
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
testImplementation "org.junit.jupiter:junit-jupiter-api:${project.junit_version}" testImplementation "org.junit.jupiter:junit-jupiter-api:${project.junit_version}"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${project.junit_version}" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${project.junit_version}"
} }
java { processResources {
withSourcesJar() inputs.property "version", project.version
withJavadocJar()
filesMatching("fabric.mod.json") {
expand "version": project.version
} }
}
tasks.withType(JavaCompile).configureEach {
// ensure that the encoding is set to UTF-8, no matter what the system default is
// this fixes some edge cases with special characters not displaying correctly
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
// If Javadoc is generated, this must be specified in that task too.
it.options.encoding = "UTF-8"
// The Minecraft launcher currently installs Java 8 for users, so your mod probably wants to target Java 8 too
// JDK 9 introduced a new way of specifying this that will make sure no newer classes or methods are used.
// We'll use that if it's available, but otherwise we'll use the older option.
def targetVersion = 17
if (JavaVersion.current().isJava9Compatible()) {
it.options.release = targetVersion
}
}
test { test {
useJUnitPlatform() useJUnitPlatform()

View File

@ -0,0 +1,18 @@
package fr.altarik.toolbox.task;
public abstract class AltarikRunnable implements Runnable {
private boolean isCancelled = false;
/**
* Warning: Some task cannot be cancelled (mostly async tasks like {@link fr.altarik.toolbox.task.asyncTasks.AsyncTasks}
* The result of this call is ignored in this case, you can still add a way to not execute its content (like if(isCancelled) return;)
*/
public void cancel() {
this.isCancelled = true;
}
public boolean isCancelled() {
return isCancelled;
}
}

View File

@ -0,0 +1,14 @@
package fr.altarik.toolbox.task;
public interface PeriodicTaskI extends TaskI {
/**
* Run a task periodically
* @param function the function to execute
* @param delay delay before starting the task
* @param period time to wait between runs
* @throws InterruptedException When executed asynchronously, task may be interrupted
* @see fr.altarik.toolbox.task.syncTasks.PeriodicSyncTask
*/
public void addTask(AltarikRunnable function, long delay, long period) throws InterruptedException;
}

View File

@ -0,0 +1,18 @@
package fr.altarik.toolbox.task;
import fr.altarik.toolbox.task.asyncTasks.AsyncTasks;
import net.fabricmc.api.ModInitializer;
public class Task implements ModInitializer {
public TaskI asyncWorkers = AsyncTasks.initialize();
@Override
public void onInitialize() {
}
public TaskI getAsyncWorkers() {
return asyncWorkers;
}
}

View File

@ -0,0 +1,7 @@
package fr.altarik.toolbox.task;
public interface TaskI extends AutoCloseable {
public void addTask(AltarikRunnable function) throws InterruptedException;
}

View File

@ -1,4 +1,7 @@
package fr.altarik.toolbox.asynctasks; package fr.altarik.toolbox.task.asyncTasks;
import fr.altarik.toolbox.task.AltarikRunnable;
import fr.altarik.toolbox.task.TaskI;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -7,7 +10,7 @@ import java.util.concurrent.TimeUnit;
/** /**
* A non-blocking small sized time-consuming tasks to executed asynchronously. * A non-blocking small sized time-consuming tasks to executed asynchronously.
*/ */
public class AsyncTasks implements AutoCloseable { public class AsyncTasks implements TaskI {
private final ExecutorService worker; private final ExecutorService worker;
@ -27,11 +30,11 @@ public class AsyncTasks implements AutoCloseable {
* *
* @return an instance of AsyncTasks * @return an instance of AsyncTasks
*/ */
public static AsyncTasks initialize(int numberOfWorker) { public static TaskI initialize(int numberOfWorker) {
return new AsyncTasks(numberOfWorker); return new AsyncTasks(numberOfWorker);
} }
public static AsyncTasks initialize() { public static TaskI initialize() {
return initialize(Runtime.getRuntime().availableProcessors()); return initialize(Runtime.getRuntime().availableProcessors());
} }
@ -65,7 +68,7 @@ public class AsyncTasks implements AutoCloseable {
* @param function task to be executed * @param function task to be executed
* @throws InterruptedException when worker thread or BlockQueue has been interrupted while waiting (which is anormal) * @throws InterruptedException when worker thread or BlockQueue has been interrupted while waiting (which is anormal)
*/ */
public void addTask(Runnable function) throws InterruptedException { public void addTask(AltarikRunnable function) throws InterruptedException {
if(worker.isTerminated() || worker.isShutdown()) { if(worker.isTerminated() || worker.isShutdown()) {
throw new InterruptedException("Worker has been terminated or shutdown, it's impossible to add new task"); throw new InterruptedException("Worker has been terminated or shutdown, it's impossible to add new task");
} }
@ -75,9 +78,11 @@ public class AsyncTasks implements AutoCloseable {
/** /**
* This method is call when you want to close workers and wait for waiting tasks to finish * This method is call when you want to close workers and wait for waiting tasks to finish
* *
* @throws UnfinishedTasksException when all tasks cannot be terminated in 10 seconds
* @throws InterruptedException if interrupted while waiting for tasks to finish
*/ */
@Override @Override
public void close() throws Exception { public void close() throws UnfinishedTasksException, InterruptedException {
worker.shutdown(); worker.shutdown();
boolean result = worker.awaitTermination(10, TimeUnit.SECONDS); boolean result = worker.awaitTermination(10, TimeUnit.SECONDS);
if(!result) { if(!result) {

View File

@ -0,0 +1,47 @@
package fr.altarik.toolbox.task.syncTasks;
import fr.altarik.toolbox.task.AltarikRunnable;
import fr.altarik.toolbox.task.TaskI;
import java.util.ArrayList;
import java.util.List;
public class PeriodicSyncTask implements TaskI, Runnable {
private ServerTickListener listener;
private List<AltarikRunnable> tasks;
private PeriodicSyncTask() {
this.listener = new ServerTickListener(this);
this.tasks = new ArrayList<>(2);
}
public static TaskI initialize() {
return new PeriodicSyncTask();
}
@Override
public void addTask(AltarikRunnable function) throws InterruptedException {
tasks.add(function);
}
@Override
public void close() throws Exception {
}
@Override
public void run() {
List<AltarikRunnable> removeList = new ArrayList<>(tasks.size());
for(AltarikRunnable task : tasks) {
if(task.isCancelled()) {
removeList.add(task);
} else {
task.run();
}
}
tasks.removeAll(removeList);
}
}

View File

@ -0,0 +1,19 @@
package fr.altarik.toolbox.task.syncTasks;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.minecraft.server.MinecraftServer;
public class ServerTickListener {
private final PeriodicSyncTask task;
public ServerTickListener(PeriodicSyncTask syncTask) {
this.task = syncTask;
ServerTickEvents.START_SERVER_TICK.register(this::onServerTick);
}
private void onServerTick(MinecraftServer minecraftServer) {
}
}

View File

@ -0,0 +1,15 @@
{
"required": true,
"minVersion": "0.8",
"package": "fr.altarik.toolbox.task.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [
],
"client": [
],
"verbose": false,
"injectors": {
"defaultRequire": 1
}
}

View File

@ -0,0 +1,34 @@
{
"schemaVersion": 1,
"id": "task",
"version": "${version}",
"name": "Task",
"description": "A mod to use as a dependency for others to schedule tasks",
"authors": [
"Altarik"
],
"contributors": [
"Legot Quentin<legotquentin@gmail.com>"
],
"contact": {
"homepage": "https://altarik.fr"
},
"license": "Altarik @ All-Rights-Reserved ",
"icon": "assets/quests/icon.png",
"environment": "*",
"entrypoints": {
"main": [
"fr.altarik.toolbox.task.Task"
]
},
"mixins": [
"Task.mixins.json"
],
"depends": {
"fabricloader": ">=0.14.12",
"fabric": "*",
"minecraft": "1.19.3",
"npcs": "2.0.1-SNAPSHOT"
}
}

View File

@ -1,6 +1,8 @@
package fr.altarik.toolbox; package fr.altarik.toolbox;
import fr.altarik.toolbox.asynctasks.AsyncTasks; import fr.altarik.toolbox.task.AltarikRunnable;
import fr.altarik.toolbox.task.TaskI;
import fr.altarik.toolbox.task.asyncTasks.AsyncTasks;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.Date; import java.util.Date;
@ -19,14 +21,17 @@ class AsyncTaskTest {
void testAsyncOp() throws Exception { void testAsyncOp() throws Exception {
int numberOfTasks = 10000; int numberOfTasks = 10000;
System.out.println("Initializing async tasks worker"); System.out.println("Initializing async tasks worker");
AsyncTasks worker = AsyncTasks.initialize(1); // only testing on a single worker, otherwise result have a high chance to not be in the order we want TaskI worker = AsyncTasks.initialize(1); // only testing on a single worker, otherwise result have a high chance to not be in the order we want
Stack<Integer> results = new Stack<>(); Stack<Integer> results = new Stack<>();
for(int i = 0; i < numberOfTasks; i++) { for(int i = 0; i < numberOfTasks; i++) {
System.out.println(log("sending task " + i)); System.out.println(log("sending task " + i));
AtomicInteger atomicInteger = new AtomicInteger(i); AtomicInteger atomicInteger = new AtomicInteger(i);
worker.addTask(() -> { worker.addTask(new AltarikRunnable() {
@Override
public void run() {
System.out.println(log(" task " + atomicInteger.get())); System.out.println(log(" task " + atomicInteger.get()));
results.push(atomicInteger.get()); results.push(atomicInteger.get());
}
}); });
} }
worker.close(); // wait until all worker terminated worker.close(); // wait until all worker terminated

View File

@ -27,6 +27,13 @@ subprojects {
} }
} }
java {
withSourcesJar()
withJavadocJar()
}
repositories { repositories {
maven { maven {
name 'altarik-snapshots' name 'altarik-snapshots'

View File

@ -1,4 +1,12 @@
org.gradle.jvmargs=-Xmx5G
junit_version=5.9.0 junit_version=5.9.0
minecraft_version=1.19.3
yarn_mappings=1.19.3+build.5
loader_version=0.14.12
fabric_version=0.70.0+1.19.3
maven_group=fr.altarik.toolbox maven_group=fr.altarik.toolbox
maven_version=2.0.0-SNAPSHOT maven_version=2.0.0-SNAPSHOT
repo_username=Altarik repo_username=Altarik

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@ -1,2 +1,13 @@
pluginManagement {
repositories {
mavenCentral()
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
gradlePluginPortal()
}
}
rootProject.name = 'Toolbox' rootProject.name = 'Toolbox'
include(':Tasks', ':Database') include(':Tasks', ':Database')