Commits

DerFrZocker authored and md_5 committed 06c0ce78cce
#1110: Make CraftMapColorCache more thread safe
No tags

src/main/java/org/bukkit/craftbukkit/map/CraftMapColorCache.java

Modified
4 4 import java.awt.Color;
5 5 import java.io.File;
6 6 import java.io.FileInputStream;
7 7 import java.io.FileOutputStream;
8 8 import java.io.IOException;
9 9 import java.io.InputStream;
10 10 import java.io.OutputStream;
11 11 import java.security.MessageDigest;
12 12 import java.security.NoSuchAlgorithmException;
13 13 import java.util.concurrent.CompletableFuture;
14 +import java.util.concurrent.atomic.AtomicBoolean;
14 15 import java.util.logging.Logger;
15 16 import java.util.zip.DeflaterOutputStream;
16 17 import java.util.zip.InflaterInputStream;
17 18 import net.minecraft.SystemUtils;
18 19 import org.bukkit.map.MapPalette;
19 20
20 21 public class CraftMapColorCache implements MapPalette.MapColorCache {
21 22
22 23 private static final String MD5_CACHE_HASH = "E88EDD068D12D39934B40E8B6B124C83";
23 24 private static final File CACHE_FILE = new File("map-color-cache.dat");
24 25 private byte[] cache;
25 26 private final Logger logger;
26 27 private boolean cached = false;
27 - private boolean running = false;
28 + private final AtomicBoolean running = new AtomicBoolean(false);
28 29
29 30 public CraftMapColorCache(Logger logger) {
30 31 this.logger = logger;
31 32 }
32 33
33 34 // Builds and prints the md5 hash of the cache, this should be run when new map colors are added to update the MD5_CACHE_HASH string
34 35 public static void main(String[] args) {
35 36 CraftMapColorCache craftMapColorCache = new CraftMapColorCache(Logger.getGlobal());
36 37 craftMapColorCache.buildCache();
37 38 try {
50 51 int first = (value & 0xF0) >> 4;
51 52 int second = value & 0x0F;
52 53 builder.append(chars[first]);
53 54 builder.append(chars[second]);
54 55 }
55 56
56 57 return builder.toString();
57 58 }
58 59
59 60 public CompletableFuture<Void> initCache() {
60 - Preconditions.checkState(!cached && !running, "Cache is already build or is currently being build");
61 + Preconditions.checkState(!cached && !running.getAndSet(true), "Cache is already build or is currently being build");
61 62
62 63 cache = new byte[256 * 256 * 256]; // Red, Green and Blue have each a range from 0 to 255 each mean we need space for 256 * 256 * 256 values
63 64 if (CACHE_FILE.exists()) {
64 65 byte[] fileContent;
65 66
66 67 try (InputStream inputStream = new InflaterInputStream(new FileInputStream(CACHE_FILE))) {
67 68 fileContent = inputStream.readAllBytes();
68 69 } catch (IOException e) {
69 70 logger.warning("Error while reading map color cache");
70 71 e.printStackTrace();
101 102 for (int g = 0; g < 256; g++) {
102 103 for (int b = 0; b < 256; b++) {
103 104 Color color = new Color(r, g, b);
104 105 cache[toInt(color)] = MapPalette.matchColor(color);
105 106 }
106 107 }
107 108 }
108 109 }
109 110
110 111 private CompletableFuture<Void> buildAndSaveCache() {
111 - running = true;
112 112 return CompletableFuture.runAsync(() -> {
113 113 buildCache();
114 114
115 115 if (!CACHE_FILE.exists()) {
116 116 try {
117 117 if (!CACHE_FILE.createNewFile()) {
118 - running = false;
119 118 cached = true;
120 119 return;
121 120 }
122 121 } catch (IOException e) {
123 122 logger.warning("Error while building map color cache");
124 123 e.printStackTrace();
125 - running = false;
126 124 cached = true;
127 125 return;
128 126 }
129 127 }
130 128
131 129 try (OutputStream outputStream = new DeflaterOutputStream(new FileOutputStream(CACHE_FILE))) {
132 130 outputStream.write(cache);
133 131 } catch (IOException e) {
134 132 logger.warning("Error while building map color cache");
135 133 e.printStackTrace();
136 - running = false;
137 134 cached = true;
138 135 return;
139 136 }
140 137
141 - running = false;
142 138 cached = true;
143 139 logger.info("Map color cache build successfully");
144 140 }, SystemUtils.backgroundExecutor());
145 141 }
146 142
147 143 private int toInt(Color color) {
148 144 return color.getRGB() & 0xFFFFFF;
149 145 }
150 146
151 147 @Override
152 148 public boolean isCached() {
153 - return cached || (!running && initCache().isDone());
149 + return cached || (!running.get() && initCache().isDone());
154 150 }
155 151
156 152 @Override
157 153 public byte matchColor(Color color) {
158 154 Preconditions.checkState(isCached(), "Cache not build jet");
159 155
160 156 return cache[toInt(color)];
161 157 }
162 158 }

Everything looks good. We'll let you know here if there's anything you should know about.

Add shortcut