问题
I am making a Minecraft clone, and I have a Map<Vector3i, Chunk>
called chunks
that stores all the loaded chunks. Every frame, this loop runs:
for(Vector3i v:chunksToUnrender){
...
CompletableFuture.runAsync(() -> {
try {
chunks.get(v).toFile(new File(String.format("res-server/regions/%s.%s.%s.mcr", v.x, v.y, v.z)));
synchronized (chunksStateGuard) {
chunks.remove(v);
}
} catch(IOException e) {
e.printStackTrace();
System.err.println("Unable to save chunk " + Utils.format3i(v));
}
});
}
The goal here is to asynchronously unload chunks. The content of Chunk.toFile(File)
is:
public void toFile(File file) throws IOException {
FileOutputStream fos = new FileOutputStream(file);
fos.write(SerializationUtils.serialize(this));
fos.flush();
fos.close();
}
However, despite using a CompletableFuture
, whenever a chunk is unloaded the game takes a framerate hit for a brief time, as it serializes and unloads the chunk. Is there any way to avoid interrupting the main thread as the background task works?
回答1:
As discussed in the comments earlier, you could further enhance the implementation with a cache that optimizes the serialization process whilst offering faster reads.
Following is a sample code that demonstrates setup and usage of a Cache, using ehCache.
Maven Dependency (within pom.xml)
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.9.0</version>
</dependency>
The ehCache Configuration (ehcache.xml)
Note - This file should be available within the classpath.
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true"
monitoring="autodetect" dynamicConfig="true">
<!-- By default, Ehcache stored the cached files in temp folder. -->
<!-- <diskStore path="java.io.tmpdir" /> -->
<!-- Ask Ehcache to store cache in this path -->
<diskStore path="c:\\temp\\ehcache" />
<!-- Sample cache named cache1
This cache contains a maximum in memory of 10000 elements, and will expire
an element if it is idle for more than 5 minutes and lives for more than
10 minutes.
If there are more than 10000 elements it will overflow to the
disk cache, which in this configuration will go to wherever java.io.tmp is
defined on your system. On a standard Linux system this will be /tmp" -->
<cache name="ApplicationContentCache"
maxEntriesLocalHeap="10000"
maxEntriesLocalDisk="1000"
eternal="false"
diskSpoolBufferSizeMB="20"
timeToIdleSeconds="300" timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU"
transactionalMode="off">
<persistence strategy="localTempSwap" />
</cache>
</ehcache>
The Various Java Classes
import java.io.Serializable;
import java.util.concurrent.CompletableFuture;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
/**
* Demo class for the caching functionality
* @author Rahul R
*
*/
public class ApplicationCacheDemo {
public static void main(String... args) {
ApplicationCacheFactory cacheFactory = ApplicationCacheFactory.instance;
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try (ApplicationCache<CachableObject> cache = cacheFactory.getApplicationCache()) {
CachableObject cacheContent = new CachableObject("A sample content");
int identity = cache.put(cacheContent);
CachableObject readContent = cache.get(identity);
System.out.println(readContent.getData());
} catch (Exception e) {
e.printStackTrace();
}
});
future.join();
}
}
/**
* The class whose objects would be cached.
* @author Rahul R
*
*/
class CachableObject implements Serializable {
private static final long serialVersionUID = 1L;
private String data = null;
public CachableObject() {
super();
}
public CachableObject(String data) {
super();
setData(data);
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((data == null) ? 0 : data.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CachableObject other = (CachableObject) obj;
if (data == null) {
if (other.data != null)
return false;
} else if (!data.equals(other.data))
return false;
return true;
}
@Override
public String toString() {
return "CachableObject [" + getData() + "]";
}
}
/**
* A singleton factory implemented through Enum
* @author Rahul R
*
*/
enum ApplicationCacheFactory {
instance;
public ApplicationCache<CachableObject> getApplicationCache() {
return new ApplicationCache<>("ApplicationContentCache");
}
}
/**
* A simplistic cache facade
* @author Rahul R
*
*/
class ApplicationCache<E> implements AutoCloseable {
private CacheManager cm = null;
private String cacheName = null;
private Cache cache = null;
public ApplicationCache(String cacheName) {
super();
setCacheName(cacheName);
initializeCache();
}
private void initializeCache() {
cm = CacheManager.getInstance();
cache = cm.getCache(getCacheName());
}
public String getCacheName() {
return cacheName;
}
public void setCacheName(String cacheName) {
this.cacheName = cacheName;
}
public int put(E value) {
int identity = value.hashCode();
cache.put(new Element(identity, value));
return identity;
}
@SuppressWarnings("unchecked")
public E get(int identity) {
E result = null;
Element element = cache.get(identity);
if (element != null) {
result = (E) element.getObjectValue();
}
return result;
}
@Override
public void close() throws Exception {
cm.shutdown();
}
}
回答2:
This was an X-Y problem. The issue was synchronous chunk loading from files.
来源:https://stackoverflow.com/questions/55265977/java-serialize-object-to-file-asynchronously