Use WeakHashMap in StaticDisplayState; Add extra options for compacting.
[SquirrelJME.git] / buildSrc / src / main / java / cc / squirreljme / plugin / multivm / RatufaCoatBuiltInTaskAction.java
blob28ab96e9c963175d9f5ad758258f40a49278b567
1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
3 // Multi-Phasic Applications: SquirrelJME
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the GNU General Public License v3+, or later.
7 // See license.mkd for licensing and copyright information.
8 // ---------------------------------------------------------------------------
10 package cc.squirreljme.plugin.multivm;
12 import cc.squirreljme.plugin.multivm.ident.SourceTargetClassifier;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.io.PrintStream;
16 import java.nio.charset.StandardCharsets;
17 import java.nio.file.Files;
18 import java.nio.file.Path;
19 import java.nio.file.StandardCopyOption;
20 import java.nio.file.StandardOpenOption;
21 import java.security.MessageDigest;
22 import java.security.NoSuchAlgorithmException;
23 import java.util.Arrays;
24 import org.gradle.api.Action;
25 import org.gradle.api.Task;
27 /**
28 * This is the action that generates the built-in ROM for RatufaCoat.
30 * @since 2021/02/25
32 public class RatufaCoatBuiltInTaskAction
33 implements Action<Task>
35 /** The column limit. */
36 private static final int _COLS =
37 40;
39 /** Compact writing table, for the quickest and smallest writes. */
40 private static final byte[][] _COMPACT =
41 new byte[256][];
43 /** The longest length. */
44 private static final int _LONGEST;
46 /** The classifier used. */
47 protected final SourceTargetClassifier classifier;
49 static
51 // Calculate the sequence of bytes which result in the smallest for
52 // a given type in C
53 byte[][] compact = RatufaCoatBuiltInTaskAction._COMPACT;
54 for (int i = 0; i < 256; i++)
56 // Find the best one
57 String least = null;
58 for (String maybe : Arrays.<String>asList(
59 Integer.toString(i),
60 "0" + Integer.toOctalString(i),
61 "0x" + Integer.toHexString(i)))
62 if (least == null || maybe.length() < least.length())
63 least = maybe;
65 // Use this one, but pre-encode it
66 compact[i] = least.getBytes(StandardCharsets.UTF_8);
69 // Go through and calculate the longest sequence
70 int longest = Integer.MIN_VALUE;
71 for (byte[] buf : compact)
72 longest = Math.max(longest, buf.length);
73 _LONGEST = longest;
76 /**
77 * Initializes the task.
79 * @param __classifier The classifier used.
80 * @throws NullPointerException On null arguments.
81 * @since 2021/02/25
83 public RatufaCoatBuiltInTaskAction(SourceTargetClassifier __classifier)
84 throws NullPointerException
86 if (__classifier == null)
87 throw new NullPointerException("NARG");
89 this.classifier = __classifier;
92 /**
93 * {@inheritDoc}
94 * @since 2021/02/25
96 @Override
97 public void execute(Task __task)
99 // Where is the ROM going?
100 Path output = __task.getOutputs().getFiles().getSingleFile().toPath();
102 // This could fail to write
103 Path tempFile = null;
106 // Setup temporary file
107 tempFile = Files.createTempFile("builtin", ".c");
109 // Generate C code from this
110 try (PrintStream out = new PrintStream(
111 Files.newOutputStream(tempFile,
112 StandardOpenOption.WRITE, StandardOpenOption.CREATE,
113 StandardOpenOption.TRUNCATE_EXISTING),
114 false, "utf-8"))
116 // Load in C header
117 try (InputStream in = RatufaCoatBuiltInTaskAction.class
118 .getResourceAsStream("header.h"))
120 byte[] buf = new byte[16384];
121 for (;;)
123 int rc = in.read(buf);
125 // EOF?
126 if (rc < 0)
127 break;
129 out.write(buf, 0, rc);
133 // Only when built-in is enabled
134 out.println();
135 out.println("#if defined(SQUIRRELJME_HAS_BUILTIN)");
136 out.println();
138 // Which source set was this created for?
139 out.print("const char* const sjme_builtInSourceSet = \"");
140 out.print(this.classifier.getSourceSet());
141 out.println("\";");
143 // Which VM Type was used?
144 out.print("const char* const sjme_builtInVmType = \"");
145 out.print(this.classifier.getVmType()
146 .vmName(VMNameFormat.PROPER_NOUN));
147 out.println("\";");
149 // What is the "nice" name of the variant?
150 out.print("const char* const sjme_builtInVariant = \"");
151 out.print(this.classifier.getBangletVariant().properNoun);
152 out.println("\";");
154 // Which banglet was used?
155 out.print("const char* const sjme_builtInBanglet = \"");
156 out.print(this.classifier.getBangletVariant().banglet);
157 out.println("\";");
159 // Declare the type
160 out.println("const sjme_jubyte sjme_builtInRomData[] = {");
162 // Read in the entire ROM file since this is much faster
163 Path inputRom = __task.getInputs()
164 .getFiles().getSingleFile().toPath();
165 byte[] romData = Files.readAllBytes(inputRom);
166 long romDate = Files.getLastModifiedTime(inputRom).toMillis();
168 // Get ROM Unique Identifier (ROM Sum)
169 byte[] romDigest = MessageDigest.getInstance("SHA-256")
170 .digest(romData);
172 // Determine file size for progress metering
173 int romSize = romData.length;
175 // Starting time for timing
176 long startNs = System.nanoTime();
178 // Output data buffer
179 int chunkySize = 0;
180 int chunkyLimit = 131072;
181 byte[] chunkyBuf = new byte[chunkyLimit + 128];
183 // Write C ROM data
184 byte[][] compact = RatufaCoatBuiltInTaskAction._COMPACT;
185 for (int i = 0, col = 0, commaBit = romSize -1;
186 i < romSize; i++)
188 // This could take awhile!
189 if (chunkySize > chunkyLimit)
191 // This could be a slow process, so allow it to be
192 // stopped! Do not check all the time since this
193 // may go quickly.
194 if (Thread.currentThread().isInterrupted())
195 throw new IOException("Conversion interrupted.");
197 // Dump the output chunk and reset it
198 out.write(chunkyBuf, 0, chunkySize);
199 chunkySize = 0;
201 // Nanoseconds per progress point
202 long currentNanos = (System.nanoTime() -
203 startNs);
204 long nanosPerIndex = currentNanos / i;
205 long estTotalNanos = nanosPerIndex * romSize;
207 // Print progress dot
208 System.err.printf("Processed %d of %d bytes " +
209 "(%d seconds remaining)...%n",
210 i, romSize,
211 (estTotalNanos - currentNanos) / 1_000_000_000L);
212 System.err.flush();
215 // Write the digit down
216 for (byte b : compact[romData[i] & 0xFF])
217 chunkyBuf[chunkySize++] = b;
219 // Do not add a final comma
220 if (i < commaBit)
221 chunkyBuf[chunkySize++] = ',';
223 // Put extra bytes on new line
224 if ((++col) >= RatufaCoatBuiltInTaskAction._COLS)
226 chunkyBuf[chunkySize++] = '\n';
227 col = 0;
231 // Dump the chunks if there is anything left
232 if (chunkySize > 0)
233 out.write(chunkyBuf, 0, chunkySize);
235 // End the type
236 out.println("};");
238 // Write ROM file size
239 out.printf("const sjme_jint sjme_builtInRomSize = %d;",
240 romData.length);
241 out.println();
243 // Write ROM ID
244 out.printf("const sjme_jbyte sjme_builtInRomId[] = " +
245 "{%s};",
246 Arrays.toString(romDigest)
247 .replace('[', ' ')
248 .replace(']', ' '));
249 out.println();
251 // Write ROM ID Length
252 out.printf("const sjme_jint sjme_builtInRomIdLen = " +
253 "SJME_JINT_C(%d);",
254 romDigest.length);
255 out.println();
257 // Write ROM date
258 out.printf("const sjme_jint sjme_builtInRomDate[] = " +
259 "{SJME_JINT_C(%d), SJME_JINT_C(%d)};",
260 (int)(romDate >>> 32), (int)romDate);
261 out.println();
263 // End Only when built-in is enabled
264 out.println();
265 out.println("#endif");
266 out.println();
268 // Flush the output before closed
269 out.flush();
272 // It worked, so place it in the output
273 Files.createDirectories(output.getParent());
274 Files.move(tempFile, output, StandardCopyOption.REPLACE_EXISTING);
277 // It did fail to write
278 catch (IOException | NoSuchAlgorithmException e)
280 throw new RuntimeException("Could not build ROM: " + output, e);
283 // Try to clear the temporary file if it exists still
284 finally
286 if (tempFile != null)
289 Files.delete(tempFile);
291 catch (IOException ignored)