Skip to content

Commit

Permalink
Initial commit: 1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
FoxSamu committed Nov 17, 2023
0 parents commit 393b5d5
Show file tree
Hide file tree
Showing 26 changed files with 2,532 additions and 0 deletions.
39 changes: 39 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/

### IntelliJ IDEA ###
.idea/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/

### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/

### VS Code ###
.vscode/

### Mac OS ###
.DS_Store
841 changes: 841 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

161 changes: 161 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# RMS (Resource Management System)
This is a very simple Java tool for games and other apps to manage resources.

# Installation
...

# Usage
There are 3 core components to the system:
1. `ResourceManager`
2. `Resource`
3. `ResourceType`

## 1. Creating the `ResourceManager`

You need at the very least a `ResourceManager`, which is a concrete class you can simply instantiate:
```java
ResourceManager mgr = new ResourceManager(path, "namespace");
```
There are 2 main things you need to create the resource manager:
- A `Path`, to the root directory that contains all your resources.
- A "standard namespace", which is the main namespace of your application. This is usually one identifier, like `rms`.

There is a special static method on `ResourceManager` that allows you to get a `Path` for a resource folder on the class path. This method uses the `ClassLoader.getResource` method, which needs a resource file in order to work. So, to get the resources from your jar file, you need to define some empty file in the root directory of your resources folder, and call `ResourceManager.classpath("/.resources_root")` if it's named `.resources_root`. The returned path is then the parent directory of that file, possibly inside of a jar file.

```java
ResourceManager mgr = new ResourceManager(ResourceManager.classpath(".resources_root"), "namespace");
```

Whenever you're done with your resources, or need to reload all of them, call `ResourceManager.dispose`.

## 2. Implementing `Resource`

The next step is to create a `Resource` implementation. `Resource` is an interface you can implement and it just has one method: `dispose`. This method is called by the `ResourceManager` upon `ResourceManager.dispose`. Usually, this method does nothing, but if your resource acquires some native resources (like off-heap memory) that must be cleaned up, then this is the place to do that. This is a resource that just contains a string:
```java
public class ContentResource implements Resource {
private final String content;

public ContentResource(String content) {
this.content = content;
}

public String content() {
return content;
}

@Override
public void dispose() {
// Not applicable
}
}
```

## 3. Loading the `Resource` using a `ResourceType`

Once you've got a `Resource` implementation, you have to define a way this resource is loaded. The `ResourceManager` assumes this resource to be in a certain folder, so all you have to do is locate it inside of that folder, read the files that must be read and turn them into your resource. Typically, the resource is in a file with the same name as the name of the resource. Resources may have path names, so you can group similar resources of the same type into a subfolder.

The loading of resources from the filesystem is done by a `ResourceType`. The `ResourceType` interface has two methods: `load` and `createFallback`. First `load` is called to locate and load the resource, and if that fails, `createFallback` should be able to create some kind of fallback in case the resource fails loading. For above example we could create the following resource type:
```java
public class ContentResourceType implements ResourceType<ContentResource> {
@Override
public ContentResource load(ResourceManager res, String ns, String name, Path directory) throws Exception {
// The `directory` parameter already accounts for the namespace, so you don't need to incorporate the
// namespace into locating the resource.
try (BufferedReader in = Files.newBufferedReader(directory.resolve(name + ".txt"), StandardCharsets.UTF_8)) {
StringWriter writer = new StringWriter();
in.transferTo(writer);
return new ContentResource(writer.toString());
}
}

@Override
public ContentResource createFallback(ResourceManager res, String ns, String name) {
// The fallback should be created from memory, it should not load from a file.
return new ContentResource("[Failed loading.]");
}
}
```
You may, however, encounter resources made of text in this specific structure quite often. There are 3 common interfaces that extend `ResourceType` which ease the loading of single-file resources (which are resources of just one file which has the name of the resource):
- `FileResourceType` is a type for loading binary files: it provides you with an `InputStream` that streams the contents of the file.
- `TextResourceType` is a type for loading text files: it provides you with a `Reader` that reads the contents of the file, by default in UTF-8.
- `StringResourceType` is an extension on `TextResourceType` that reads the entire file into a `String`, which it then provides to you.

Above example could benefit from a `StringResourceType`:
```java
public class ContentResourceType implements StringResourceType<ContentResource> {
@Override
public ContentResource load(ResourceManager res, String ns, String name, String content) {
return new ContentResource(content);
}

@Override
public String extension() {
return "txt";
}

@Override
public ContentResource createFallback(ResourceManager res, String ns, String name) {
return new ContentResource("[Failed loading.]");
}
}
```
`StringResourceType` does the exact `Reader.transferTo(StringWriter)` operation to provide us the string content. We don't need to do this ourselves.

Whereas you will have multiple instances of your `Resource` implementation, you only have one matching `ResourceType`. So typically you'd create `public static final` field somewhere with its instance because you still need it later to retrieve resources:

```java
public static final ContentResourceType INSTANCE = new ContentResourceType();
```

## 4. Loading a resource

When you got a `ResourceManager`, `Resource` and `ResourceType`, the next thing is loading the resources. First you will have to tell your `ResourceManager` about your `ResourceType`. This is so it can cache resources properly. You do this using the `register` method:
```java
// ...create resource manager here...

mgr.register(ContentResourceType.INSTANCE, "content");
```
You pass a directory name, which is the directory in which the resources are. The resource is then located in `<root path>/<namespace>/<type directory>/`, where:
- `<root path>` is the path given when creating the resource manager
- `<namespace>` is the namespace of the resource
- `<type directory>` is the directory name given when registering a resource type to the resource manager

You can now retrieve a resource using `ResourceManager.get`:

```java
ContentResource myContent = mgr.get(ContentResourceType.INSTANCE, "namespace", "my_content");
// This will load <root path>/namespace/content/my_content.txt

// You can leave out the namespace, it will fall back on the namespace given when creating the ResourceManager
ContentResource myContent = mgr.get(ContentResourceType.INSTANCE, "my_content");
```


## 5. Extra: Using handles

In some occasion, you may want to reload all the resources in your application. This means all the `Resource` instances change. You will have to re-obtain all the resources yourself, which can be quite annoying to do. That is, unless you use `Handle`s. A `Handle` is a reference to a resource. It will store the resource for quick access but it's controlled by the resource manager and reset upon `dispose()`. Instead of calling `ResourceManager.get`, you can call `ResourceManager.handle` in exactly the same way to get a handle instead. You can then store this handle anywhere in your app without ever having to think about reloading it again. Another advantage of using handles is that they're lazily loaded: it will load the resource only when you request it using `Handle.get`. A handle is simply just a glorified `Supplier`.
```java
private final Handle<ContentResource> myContent = mgr.handle(ContentResourceType.INSTANCE, "my_content");
...
ContentResource instance = myContent.get();
```

Now you can call `dispose` at any time to simply unload all resources. RMS will invalidate all handles and the next time you call `get` on any of them, it will reload the resource. In fact, you can call `dispose` and give a new root path, and the resource will load as usual from the new path.

# License
Licensed under the LGPLv3 license. For the full LGPL+GPLv3 license, see `LICENSE`.

Copyright (C) 2023 Samū

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
71 changes: 71 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright (C) 2023 Samū
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

plugins {
id("java")
id("maven-publish")
}

group = "dev.runefox"
version = "1.0"

repositories {
mavenCentral()
}

dependencies {
testImplementation(platform("org.junit:junit-bom:5.9.1"))
testImplementation("org.junit.jupiter:junit-jupiter")
}

tasks.test {
useJUnitPlatform()
}

java {
withSourcesJar()
withJavadocJar()
}

publishing {
publications {
create<MavenPublication>("maven") {
groupId = "${project.group}"
artifactId = project.name
version = "${project.version}"

from(components["java"])
}
}
repositories {
maven {
// You should have received these credentials when given permission
// to the Runefox Maven repository
val rfxMavenHost: String by properties
val rfxMavenUser: String by properties
val rfxMavenPass: String by properties

name = "RfxMaven"
url = uri(rfxMavenHost)
credentials {
username = rfxMavenUser
password = rfxMavenPass
}
}
}
}

Binary file added gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
23 changes: 23 additions & 0 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#
# Copyright (C) 2023 Sam?
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#

#Thu Nov 16 19:31:30 CET 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading

0 comments on commit 393b5d5

Please sign in to comment.