map, T value, long minId, long maxId) {
+ long newTransactionId;
+ do {
+ newTransactionId = getRandomId(minId, maxId);
+ } while (map.putIfAbsent(newTransactionId, value) != null);
+ return newTransactionId;
+ }
+
+ private static long getRandomId(long minId, long maxId) {
+ return ThreadLocalRandom.current().nextLong(minId, maxId);
+ }
+}
diff --git a/src/main/java/it/cavallium/rockserver/core/impl/SafeShutdown.java b/src/main/java/it/cavallium/rockserver/core/impl/SafeShutdown.java
new file mode 100644
index 0000000..7e80cb4
--- /dev/null
+++ b/src/main/java/it/cavallium/rockserver/core/impl/SafeShutdown.java
@@ -0,0 +1,55 @@
+package it.cavallium.rockserver.core.impl;
+
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.LongAdder;
+
+public class SafeShutdown implements AutoCloseable {
+
+ private volatile boolean closing;
+
+ private final LongAdder pendingOps = new LongAdder();
+
+ public void beginOp() {
+ if (closing) {
+ throw new IllegalStateException("Closed");
+ }
+ pendingOps.increment();
+ }
+
+ public void endOp() {
+ pendingOps.decrement();
+ }
+
+ public void closeAndWait(long timeoutMs) throws TimeoutException {
+ this.closing = true;
+ waitForExit(timeoutMs);
+ }
+
+ public void waitForExit(long timeoutMs) throws TimeoutException {
+ try {
+ long startMs = System.nanoTime();
+ while (pendingOps.sum() > 0 && System.nanoTime() - startMs < (timeoutMs * 1000000L)) {
+ //noinspection BusyWait
+ Thread.sleep(10);
+ }
+ if (pendingOps.sum() > 0) {
+ throw new TimeoutException();
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void close() {
+ try {
+ closeAndWait(Long.MAX_VALUE);
+ } catch (TimeoutException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public boolean isOpen() {
+ return !closing;
+ }
+}
diff --git a/src/main/java/it/cavallium/rockserver/core/impl/SlicingArena.java b/src/main/java/it/cavallium/rockserver/core/impl/SlicingArena.java
new file mode 100644
index 0000000..f75e627
--- /dev/null
+++ b/src/main/java/it/cavallium/rockserver/core/impl/SlicingArena.java
@@ -0,0 +1,28 @@
+package it.cavallium.rockserver.core.impl;
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.SegmentAllocator;
+
+public class SlicingArena implements Arena {
+
+ final Arena arena = Arena.ofConfined();
+ final SegmentAllocator slicingAllocator;
+
+ public SlicingArena(long size) {
+ slicingAllocator = SegmentAllocator.slicingAllocator(arena.allocate(size));
+ }
+
+ public MemorySegment allocate(long byteSize, long byteAlignment) {
+ return slicingAllocator.allocate(byteSize, byteAlignment);
+ }
+
+ public MemorySegment.Scope scope() {
+ return arena.scope();
+ }
+
+ public void close() {
+ arena.close();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/it/cavallium/rockserver/core/impl/XXHash32.java b/src/main/java/it/cavallium/rockserver/core/impl/XXHash32.java
new file mode 100644
index 0000000..122e1bf
--- /dev/null
+++ b/src/main/java/it/cavallium/rockserver/core/impl/XXHash32.java
@@ -0,0 +1,48 @@
+package it.cavallium.rockserver.core.impl;
+
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.MemorySegment;
+
+/**
+ * A 32-bits hash.
+ *
+ * Instances of this class are thread-safe.
+ */
+public abstract class XXHash32 {
+
+ /**
+ * Compute the 32-bits hash of buf[off:off+len]
using seed
+ * seed
.
+ */
+ public abstract int hash(byte[] buf, int off, int len, int seed);
+
+ /**
+ * Compute the big-endian 32-bits hash of buf[off:off+len]
using seed
+ * seed
.
+ */
+ public abstract MemorySegment hash(Arena arena, MemorySegment buf, int off, int len, int seed);
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+ public static XXHash32 getInstance() {
+ return XXHash32JavaSafe.INSTANCE;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/it/cavallium/rockserver/core/impl/XXHash32JavaSafe.java b/src/main/java/it/cavallium/rockserver/core/impl/XXHash32JavaSafe.java
new file mode 100644
index 0000000..6116286
--- /dev/null
+++ b/src/main/java/it/cavallium/rockserver/core/impl/XXHash32JavaSafe.java
@@ -0,0 +1,222 @@
+package it.cavallium.rockserver.core.impl;
+
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static it.cavallium.rockserver.core.impl.XXHashUtils.PRIME1;
+import static it.cavallium.rockserver.core.impl.XXHashUtils.PRIME2;
+import static it.cavallium.rockserver.core.impl.XXHashUtils.PRIME3;
+import static it.cavallium.rockserver.core.impl.XXHashUtils.PRIME4;
+import static it.cavallium.rockserver.core.impl.XXHashUtils.PRIME5;
+import static java.lang.Integer.rotateLeft;
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
+import java.lang.foreign.ValueLayout.OfByte;
+import java.lang.foreign.ValueLayout.OfInt;
+import java.nio.ByteOrder;
+
+/**
+ * Safe Java implementation of {@link XXHash32}.
+ */
+public final class XXHash32JavaSafe extends XXHash32 {
+
+
+ public static final ByteOrder NATIVE_BYTE_ORDER = ByteOrder.nativeOrder();
+ public static final XXHash32 INSTANCE = new XXHash32JavaSafe();
+ private static final OfInt INT_LE = ValueLayout.JAVA_INT.withOrder(ByteOrder.LITTLE_ENDIAN);
+ private static final OfInt INT_BE = ValueLayout.JAVA_INT.withOrder(ByteOrder.BIG_ENDIAN);
+ private static final OfByte BYTE_BE = ValueLayout.JAVA_BYTE.withOrder(ByteOrder.BIG_ENDIAN);
+
+ @Override
+ public int hash(byte[] buf, int off, int len, int seed) {
+ checkRange(buf, off, len);
+
+ final int end = off + len;
+ int h32;
+
+ if (len >= 16) {
+ final int limit = end - 16;
+ int v1 = seed + PRIME1 + PRIME2;
+ int v2 = seed + PRIME2;
+ int v3 = seed + 0;
+ int v4 = seed - PRIME1;
+ do {
+ v1 += readIntLE(buf, off) * PRIME2;
+ v1 = rotateLeft(v1, 13);
+ v1 *= PRIME1;
+ off += 4;
+
+ v2 += readIntLE(buf, off) * PRIME2;
+ v2 = rotateLeft(v2, 13);
+ v2 *= PRIME1;
+ off += 4;
+
+ v3 += readIntLE(buf, off) * PRIME2;
+ v3 = rotateLeft(v3, 13);
+ v3 *= PRIME1;
+ off += 4;
+
+ v4 += readIntLE(buf, off) * PRIME2;
+ v4 = rotateLeft(v4, 13);
+ v4 *= PRIME1;
+ off += 4;
+ } while (off <= limit);
+
+ h32 = rotateLeft(v1, 1) + rotateLeft(v2, 7) + rotateLeft(v3, 12) + rotateLeft(v4, 18);
+ } else {
+ h32 = seed + PRIME5;
+ }
+
+ h32 += len;
+
+ while (off <= end - 4) {
+ h32 += readIntLE(buf, off) * PRIME3;
+ h32 = rotateLeft(h32, 17) * PRIME4;
+ off += 4;
+ }
+
+ while (off < end) {
+ h32 += (buf[off] & 0xFF) * PRIME5;
+ h32 = rotateLeft(h32, 11) * PRIME1;
+ ++off;
+ }
+
+ h32 ^= h32 >>> 15;
+ h32 *= PRIME2;
+ h32 ^= h32 >>> 13;
+ h32 *= PRIME3;
+ h32 ^= h32 >>> 16;
+
+ return h32;
+ }
+
+ @Override
+ public MemorySegment hash(Arena arena, MemorySegment buf, int off, int len, int seed) {
+ checkRange(buf, off, len);
+
+ final int end = off + len;
+ int h32;
+
+ if (len >= 16) {
+ final int limit = end - 16;
+ int v1 = seed + PRIME1 + PRIME2;
+ int v2 = seed + PRIME2;
+ int v3 = seed + 0;
+ int v4 = seed - PRIME1;
+ do {
+ v1 += readIntLE(buf, off) * PRIME2;
+ v1 = rotateLeft(v1, 13);
+ v1 *= PRIME1;
+ off += 4;
+
+ v2 += readIntLE(buf, off) * PRIME2;
+ v2 = rotateLeft(v2, 13);
+ v2 *= PRIME1;
+ off += 4;
+
+ v3 += readIntLE(buf, off) * PRIME2;
+ v3 = rotateLeft(v3, 13);
+ v3 *= PRIME1;
+ off += 4;
+
+ v4 += readIntLE(buf, off) * PRIME2;
+ v4 = rotateLeft(v4, 13);
+ v4 *= PRIME1;
+ off += 4;
+ } while (off <= limit);
+
+ h32 = rotateLeft(v1, 1) + rotateLeft(v2, 7) + rotateLeft(v3, 12) + rotateLeft(v4, 18);
+ } else {
+ h32 = seed + PRIME5;
+ }
+
+ h32 += len;
+
+ while (off <= end - 4) {
+ h32 += readIntLE(buf, off) * PRIME3;
+ h32 = rotateLeft(h32, 17) * PRIME4;
+ off += 4;
+ }
+
+ while (off < end) {
+ h32 += (buf.get(BYTE_BE, off) & 0xFF) * PRIME5;
+ h32 = rotateLeft(h32, 11) * PRIME1;
+ ++off;
+ }
+
+ h32 ^= h32 >>> 15;
+ h32 *= PRIME2;
+ h32 ^= h32 >>> 13;
+ h32 *= PRIME3;
+ h32 ^= h32 >>> 16;
+
+ return arena.allocate(INT_BE, h32);
+ }
+
+ private static void checkRange(byte[] buf, int off) {
+ if (off < 0 || off >= buf.length) {
+ throw new ArrayIndexOutOfBoundsException(off);
+ }
+ }
+
+ private static void checkRange(MemorySegment buf, int off) {
+ if (off < 0 || off >= buf.byteSize()) {
+ throw new ArrayIndexOutOfBoundsException(off);
+ }
+ }
+
+ private static void checkRange(byte[] buf, int off, int len) {
+ checkLength(len);
+ if (len > 0) {
+ checkRange(buf, off);
+ checkRange(buf, off + len - 1);
+ }
+ }
+
+ private static void checkRange(MemorySegment buf, int off, int len) {
+ checkLength(len);
+ if (len > 0) {
+ checkRange(buf, off);
+ checkRange(buf, off + len - 1);
+ }
+ }
+
+ private static void checkLength(int len) {
+ if (len < 0) {
+ throw new IllegalArgumentException("lengths must be >= 0");
+ }
+ }
+
+ private static int readIntBE(byte[] buf, int i) {
+ return ((buf[i] & 0xFF) << 24) | ((buf[i+1] & 0xFF) << 16) | ((buf[i+2] & 0xFF) << 8) | (buf[i+3] & 0xFF);
+ }
+
+ private static int readIntLE(byte[] buf, int i) {
+ return (buf[i] & 0xFF) | ((buf[i+1] & 0xFF) << 8) | ((buf[i+2] & 0xFF) << 16) | ((buf[i+3] & 0xFF) << 24);
+ }
+
+ private static int readIntLE(MemorySegment buf, int i) {
+ return buf.get(INT_LE, i);
+ }
+
+ private static int readInt(byte[] buf, int i) {
+ if (NATIVE_BYTE_ORDER == ByteOrder.BIG_ENDIAN) {
+ return readIntBE(buf, i);
+ } else {
+ return readIntLE(buf, i);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/it/cavallium/rockserver/core/impl/XXHashUtils.java b/src/main/java/it/cavallium/rockserver/core/impl/XXHashUtils.java
new file mode 100644
index 0000000..c96b9d6
--- /dev/null
+++ b/src/main/java/it/cavallium/rockserver/core/impl/XXHashUtils.java
@@ -0,0 +1,26 @@
+package it.cavallium.rockserver.core.impl;
+
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+enum XXHashUtils {
+ ;
+
+ static final int PRIME1 = -1640531535;
+ static final int PRIME2 = -2048144777;
+ static final int PRIME3 = -1028477379;
+ static final int PRIME4 = 668265263;
+ static final int PRIME5 = 374761393;
+
+}
\ No newline at end of file
diff --git a/src/main/java/it/cavallium/rockserver/core/impl/rocksdb/REntry.java b/src/main/java/it/cavallium/rockserver/core/impl/rocksdb/REntry.java
new file mode 100644
index 0000000..eb62771
--- /dev/null
+++ b/src/main/java/it/cavallium/rockserver/core/impl/rocksdb/REntry.java
@@ -0,0 +1,13 @@
+package it.cavallium.rockserver.core.impl.rocksdb;
+
+import java.io.Closeable;
+import org.rocksdb.AbstractNativeReference;
+
+public record REntry(T val, RocksDBObjects objs) implements Closeable {
+
+ @Override
+ public void close() {
+ val.close();
+ objs.close();
+ }
+}
diff --git a/src/main/java/it/cavallium/rockserver/core/impl/rocksdb/RocksDBObjects.java b/src/main/java/it/cavallium/rockserver/core/impl/rocksdb/RocksDBObjects.java
new file mode 100644
index 0000000..364918c
--- /dev/null
+++ b/src/main/java/it/cavallium/rockserver/core/impl/rocksdb/RocksDBObjects.java
@@ -0,0 +1,37 @@
+package it.cavallium.rockserver.core.impl.rocksdb;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class RocksDBObjects implements AutoCloseable {
+ private final List refs;
+
+ public RocksDBObjects(int size) {
+ this.refs = new ArrayList<>(size);
+ }
+ public RocksDBObjects() {
+ this.refs = new ArrayList<>();
+ }
+
+ public RocksDBObjects(AutoCloseable... refs) {
+ this(refs.length);
+ for (AutoCloseable ref : refs) {
+ add(ref);
+ }
+ }
+
+ public void add(AutoCloseable ref) {
+ this.refs.add(ref);
+ }
+
+ @Override
+ public void close() {
+ for (int i = refs.size() - 1; i >= 0; i--) {
+ try {
+ refs.get(i).close();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/src/main/java/it/cavallium/rockserver/core/impl/rocksdb/TransactionalDB.java b/src/main/java/it/cavallium/rockserver/core/impl/rocksdb/TransactionalDB.java
new file mode 100644
index 0000000..0966346
--- /dev/null
+++ b/src/main/java/it/cavallium/rockserver/core/impl/rocksdb/TransactionalDB.java
@@ -0,0 +1,217 @@
+package it.cavallium.rockserver.core.impl.rocksdb;
+
+import java.io.Closeable;
+import java.io.IOException;
+import org.rocksdb.OptimisticTransactionDB;
+import org.rocksdb.OptimisticTransactionOptions;
+import org.rocksdb.RocksDB;
+import org.rocksdb.RocksDBException;
+import org.rocksdb.Transaction;
+import org.rocksdb.TransactionDB;
+import org.rocksdb.TransactionOptions;
+import org.rocksdb.WriteOptions;
+
+public sealed interface TransactionalDB extends Closeable {
+
+ static TransactionalDB create(RocksDB db) {
+ return switch (db) {
+ case OptimisticTransactionDB optimisticTransactionDB -> new OptimisticTransactionalDB(optimisticTransactionDB);
+ case TransactionDB transactionDB -> new PessimisticTransactionalDB(transactionDB);
+ default -> throw new UnsupportedOperationException("This database is not transactional");
+ };
+ }
+
+ TransactionalOptions createTransactionalOptions();
+
+ RocksDB get();
+ /**
+ * Starts a new Transaction.
+ *
+ * Caller is responsible for calling {@link #close()} on the returned
+ * transaction when it is no longer needed.
+ *
+ * @param writeOptions Any write options for the transaction
+ * @return a new transaction
+ */
+ Transaction beginTransaction(final WriteOptions writeOptions);
+
+ /**
+ * Starts a new Transaction.
+ *
+ * Caller is responsible for calling {@link #close()} on the returned
+ * transaction when it is no longer needed.
+ *
+ * @param writeOptions Any write options for the transaction
+ * @param transactionOptions Any options for the transaction
+ * @return a new transaction
+ */
+ Transaction beginTransaction(final WriteOptions writeOptions,
+ final TransactionalOptions transactionOptions);
+
+ /**
+ * Starts a new Transaction.
+ *
+ * Caller is responsible for calling {@link #close()} on the returned
+ * transaction when it is no longer needed.
+ *
+ * @param writeOptions Any write options for the transaction
+ * @param oldTransaction this Transaction will be reused instead of allocating
+ * a new one. This is an optimization to avoid extra allocations
+ * when repeatedly creating transactions.
+ * @return The oldTransaction which has been reinitialized as a new
+ * transaction
+ */
+ Transaction beginTransaction(final WriteOptions writeOptions,
+ final Transaction oldTransaction);
+
+ /**
+ * Starts a new Transaction.
+ *
+ * Caller is responsible for calling {@link #close()} on the returned
+ * transaction when it is no longer needed.
+ *
+ * @param writeOptions Any write options for the transaction
+ * @param transactionOptions Any options for the transaction
+ * @param oldTransaction this Transaction will be reused instead of allocating
+ * a new one. This is an optimization to avoid extra allocations
+ * when repeatedly creating transactions.
+ * @return The oldTransaction which has been reinitialized as a new
+ * transaction
+ */
+ Transaction beginTransaction(final WriteOptions writeOptions,
+ final TransactionalOptions transactionOptions, final Transaction oldTransaction);
+
+ sealed interface TransactionalOptions extends Closeable {
+
+ @Override
+ void close();
+ }
+
+ final class PessimisticTransactionalDB implements TransactionalDB {
+
+ private final TransactionDB db;
+
+ public PessimisticTransactionalDB(TransactionDB db) {
+ this.db = db;
+ }
+
+ @Override
+ public TransactionalOptions createTransactionalOptions() {
+ return new TransactionalOptionsPessimistic(new TransactionOptions());
+ }
+
+ @Override
+ public RocksDB get() {
+ return db;
+ }
+
+ @Override
+ public Transaction beginTransaction(WriteOptions writeOptions) {
+ return db.beginTransaction(writeOptions);
+ }
+
+ @Override
+ public Transaction beginTransaction(WriteOptions writeOptions, TransactionalOptions transactionOptions) {
+ return db.beginTransaction(writeOptions,
+ ((TransactionalOptionsPessimistic) transactionOptions).transactionOptions
+ );
+ }
+
+ @Override
+ public Transaction beginTransaction(WriteOptions writeOptions, Transaction oldTransaction) {
+ return db.beginTransaction(writeOptions, oldTransaction);
+ }
+
+ @Override
+ public Transaction beginTransaction(WriteOptions writeOptions,
+ TransactionalOptions transactionOptions,
+ Transaction oldTransaction) {
+ return db.beginTransaction(writeOptions,
+ ((TransactionalOptionsPessimistic) transactionOptions).transactionOptions,
+ oldTransaction
+ );
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ db.closeE();
+ } catch (RocksDBException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private record TransactionalOptionsPessimistic(TransactionOptions transactionOptions) implements
+ TransactionalOptions {
+
+ @Override
+ public void close() {
+ transactionOptions.close();
+ }
+ }
+ }
+
+ final class OptimisticTransactionalDB implements TransactionalDB {
+
+ private final OptimisticTransactionDB db;
+
+ public OptimisticTransactionalDB(OptimisticTransactionDB db) {
+ this.db = db;
+ }
+
+ @Override
+ public TransactionalOptions createTransactionalOptions() {
+ return new TransactionalOptionsOptimistic(new OptimisticTransactionOptions());
+ }
+
+ @Override
+ public RocksDB get() {
+ return db;
+ }
+
+ @Override
+ public Transaction beginTransaction(WriteOptions writeOptions) {
+ return db.beginTransaction(writeOptions);
+ }
+
+ @Override
+ public Transaction beginTransaction(WriteOptions writeOptions, TransactionalOptions transactionOptions) {
+ return db.beginTransaction(writeOptions,
+ ((TransactionalOptionsOptimistic) transactionOptions).transactionOptions
+ );
+ }
+
+ @Override
+ public Transaction beginTransaction(WriteOptions writeOptions, Transaction oldTransaction) {
+ return db.beginTransaction(writeOptions, oldTransaction);
+ }
+
+ @Override
+ public Transaction beginTransaction(WriteOptions writeOptions,
+ TransactionalOptions transactionOptions,
+ Transaction oldTransaction) {
+ return db.beginTransaction(writeOptions,
+ ((TransactionalOptionsOptimistic) transactionOptions).transactionOptions,
+ oldTransaction
+ );
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ db.closeE();
+ } catch (RocksDBException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private record TransactionalOptionsOptimistic(OptimisticTransactionOptions transactionOptions) implements
+ TransactionalOptions {
+
+ @Override
+ public void close() {
+ transactionOptions.close();
+ }
+ }
+ }
+}
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
new file mode 100644
index 0000000..eebafa1
--- /dev/null
+++ b/src/main/java/module-info.java
@@ -0,0 +1,19 @@
+module rockserver.core {
+ requires rocksdbjni;
+ requires net.sourceforge.argparse4j;
+ requires inet.ipaddr;
+ requires java.logging;
+ requires typesafe.config;
+ requires org.jetbrains.annotations;
+ requires high.scale.lib;
+ requires org.github.gestalt.core;
+ requires org.github.gestalt.hocon;
+
+ exports it.cavallium.rockserver.core.client;
+ exports it.cavallium.rockserver.core.common;
+ exports it.cavallium.rockserver.core.config;
+ opens it.cavallium.rockserver.core.resources;
+ opens it.cavallium.rockserver.core.config to org.github.gestalt.core, org.github.gestalt.hocon;
+ exports it.cavallium.rockserver.core.impl.rocksdb;
+ exports it.cavallium.rockserver.core.impl;
+}
\ No newline at end of file
diff --git a/src/main/resources/it/cavallium/rockserver/core/resources/default.conf b/src/main/resources/it/cavallium/rockserver/core/resources/default.conf
new file mode 100644
index 0000000..cd993da
--- /dev/null
+++ b/src/main/resources/it/cavallium/rockserver/core/resources/default.conf
@@ -0,0 +1,95 @@
+database: {
+ global: {
+ # Keep false unless you have a legacy database
+ enable-column-bug: false
+ # Enable to adapt the database to spinning disk
+ spinning: false
+ # Error checking
+ checksum: true
+ # Use direct I/O in RocksDB databases (Higher I/O read throughput but OS cache is not used, less swapping, less memory pressure)
+ use-direct-io: true
+ # Allow memory mapped (mmap) RocksDB databases (High OS cache usage if direct I/O is not enabled)
+ allow-rocksdb-memory-mapping: false
+ # Maximum open files for each RocksDB database instance. -1 is infinite.
+ # If the maximum open files count is -1, the initial startup time will be slower.
+ # If "cacheIndexAndFilterBlocks" is false, the memory will rise when the number of open files rises.
+ maximum-open-files: -1
+ optimistic: true
+ # Database block cache size
+ block-cache: 512MiB
+ # Database write buffer manager size
+ # You should enable this option if you are using direct I/O or spinning disks
+ write-buffer-manager: 64MiB
+ # Log data path
+ log-path: ./logs
+ # Write-Ahead-Log data path
+ wal-path: ./wal
+ fallback-column-options: {
+ # RocksDB data levels
+ # Available compression types: PLAIN, SNAPPY, LZ4, LZ4_HC, ZSTD, ZLIB, BZLIB2
+ levels: [
+ {
+ compression: LZ4
+ max-dict-bytes: 0
+ }
+ {
+ compression: LZ4
+ max-dict-bytes: 0
+ }
+ {
+ compression: ZSTD
+ max-dict-bytes: 0
+ }
+ {
+ compression: ZSTD
+ max-dict-bytes: 0
+ }
+ {
+ compression: ZSTD
+ max-dict-bytes: 0
+ }
+ {
+ compression: ZSTD
+ max-dict-bytes: 0
+ }
+ {
+ compression: ZSTD
+ # Maximum compression dictionary bytes per-sst
+ max-dict-bytes: 32KiB
+ }
+ ]
+ # Memtable memory budget for RocksDB
+ # Used to optimize compactions and avoid write stalls
+ memtable-memory-budget-bytes: 128MiB
+ # Disable to reduce IOWAIT and make the read/writes faster
+ # Enable to reduce ram usage
+ # If maximum-open-files is != -1, this option must be set to true,
+ # otherwise the indexes and filters will be unloaded often
+ cache-index-and-filter-blocks: true
+ # Disable to reduce IOWAIT and make the read/writes faster
+ # Enable to reduce ram usage
+ partition-filters: false
+ # Bloom filter.
+ bloom-filter: {
+ # Bits per key. This will determine bloom memory size: bitsPerKey * totalKeys
+ bits-per-key: 10
+ # Disable bloom for the bottommost level, this reduces the memory size to 1/10
+ optimize-for-hits: false
+ }
+ # Use relatively larger block sizes to reduce index block size.
+ # You should use at least 64KB block size.
+ # You can consider 256KB or even 512KB.
+ # The downside of using large blocks is that RAM is wasted in the block cache.
+ block-size: 16KiB
+ # This should be kept to null if write-buffer-manager is set,
+ # or if you want to use the "memtable-memory-budget-size" logic.
+ # Remember that there are "max-write-buffer-number" in memory, 2 by default
+ write-buffer-size: 200MiB
+ }
+ column-options: [
+ ${database.global.fallback-column-options} {
+ name: "default"
+ }
+ ]
+ }
+}
diff --git a/src/test/java/it/cavallium/rockserver/core/impl/test/EmbeddedDBTest.java b/src/test/java/it/cavallium/rockserver/core/impl/test/EmbeddedDBTest.java
new file mode 100644
index 0000000..b558454
--- /dev/null
+++ b/src/test/java/it/cavallium/rockserver/core/impl/test/EmbeddedDBTest.java
@@ -0,0 +1,52 @@
+package it.cavallium.rockserver.core.impl.test;
+
+import it.cavallium.rockserver.core.client.EmbeddedConnection;
+import it.cavallium.rockserver.core.common.Callback.CallbackDelta;
+import it.cavallium.rockserver.core.common.ColumnSchema;
+import it.cavallium.rockserver.core.common.Delta;
+import java.io.File;
+import java.io.IOException;
+import java.lang.foreign.MemorySegment;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.stream.Stream;
+
+class EmbeddedDBTest {
+
+ private Path dir;
+ private EmbeddedConnection db;
+
+ @org.junit.jupiter.api.BeforeEach
+ void setUp() throws IOException {
+ this.dir = Files.createTempDirectory("db-test");
+ db = new EmbeddedConnection(dir, "test", null);
+ }
+
+ @org.junit.jupiter.api.AfterEach
+ void tearDown() throws IOException {
+ try (Stream walk = Files.walk(dir)) {
+ db.close();
+ walk.sorted(Comparator.reverseOrder())
+ .map(Path::toFile)
+ .peek(System.out::println)
+ .forEach(File::delete);
+ }
+ }
+
+ @org.junit.jupiter.api.Test
+ void put() {
+ var colId = db.createColumn("put-1", new ColumnSchema(new int[]{16, 16, 16, 32, 32}, 2, true));
+ db.put(0, colId, null, null, new CallbackDelta() {
+ @Override
+ public void onSuccess(Delta previous) {
+
+ }
+ });
+ db.deleteColumn(colId);
+ }
+
+ @org.junit.jupiter.api.Test
+ void get() {
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/it/cavallium/rockserver/core/test/XXHash32Test.java b/src/test/java/it/cavallium/rockserver/core/test/XXHash32Test.java
new file mode 100644
index 0000000..8c53501
--- /dev/null
+++ b/src/test/java/it/cavallium/rockserver/core/test/XXHash32Test.java
@@ -0,0 +1,34 @@
+package it.cavallium.rockserver.core.test;
+
+import it.cavallium.rockserver.core.impl.XXHash32;
+import java.lang.foreign.Arena;
+import java.lang.foreign.ValueLayout;
+import java.lang.foreign.ValueLayout.OfByte;
+import java.nio.ByteOrder;
+import java.util.concurrent.ThreadLocalRandom;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class XXHash32Test {
+
+ public static void main(String[] args) {
+ new XXHash32Test().test();
+ }
+
+ @Test
+ public void test() {
+ var safeXxhash32 = net.jpountz.xxhash.XXHashFactory.safeInstance().hash32();
+ var myXxhash32 = XXHash32.getInstance();
+ for (int runs = 0; runs < 3; runs++) {
+ for (int len = 0; len < 600; len++) {
+ byte[] bytes = new byte[len];
+ ThreadLocalRandom.current().nextBytes(bytes);
+ var hash = safeXxhash32.hash(bytes, 0, bytes.length, Integer.MIN_VALUE);
+ var a = Arena.global();
+ var result = myXxhash32.hash(a, a.allocateArray(OfByte.JAVA_BYTE, bytes), 0, bytes.length, Integer.MIN_VALUE);
+ var resultInt = result.get(ValueLayout.JAVA_INT.withOrder(ByteOrder.BIG_ENDIAN), 0);
+ Assertions.assertEquals(hash, resultInt);
+ }
+ }
+ }
+}
diff --git a/src/test/java/module-info.java b/src/test/java/module-info.java
new file mode 100644
index 0000000..f43f685
--- /dev/null
+++ b/src/test/java/module-info.java
@@ -0,0 +1,7 @@
+module rockserver.core.test {
+ requires org.lz4.java;
+ requires rockserver.core;
+ requires org.junit.jupiter.api;
+ opens it.cavallium.rockserver.core.test;
+ opens it.cavallium.rockserver.core.impl.test;
+}
\ No newline at end of file