I created the following to draw a moving car on JavaFX
Free Starter Classes below MIT License
package application;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Logger;
import javafx.scene.image.Image;
import util.GraphicsUtil;
public class RenderEngine implements Runnable {
public static Logger logger = Logger.getLogger(RenderEngine.class.getName());
private ConcurrentLinkedQueue<Image> concurrentImageQueue;
public RenderEngine(ConcurrentLinkedQueue<Image> concurrentImageQueue) {
this.concurrentImageQueue = concurrentImageQueue;
}
@Override
public void run() {
renderFrameLoop();
}
public void renderFrameLoop() {
while (true) {
BufferedImage bufferedImage = renderFrame();
Image image = GraphicsUtil.convertBufferedImageToImage(bufferedImage);
concurrentImageQueue.add(image);
// logger.info("Frame rendered");
try {
Thread.sleep(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private int carOffset=0;
public BufferedImage renderFrame() {
BufferedImage bufferedImage = new BufferedImage(1000, 1000, BufferedImage.TYPE_INT_RGB);
Graphics2D g2D = bufferedImage.createGraphics();
g2D.setColor(Color.BLACK);
g2D.fillRect(0, 0, 1000, 1000);
g2D.setColor(Color.GREEN); // Draw Green Ground Height 400 to 1000
g2D.fillRect(0, 400, 1000, 1000);
g2D.setColor(Color.BLUE); // Draw Blue Sky Height 0 to 400
g2D.fillRect(0, 0, 1000, 400);
drawCar(g2D);
return bufferedImage;
}
public void drawCar(Graphics2D g2D) {
carOffset+=10;
// Draw Body of Car, RED
g2D.setColor(Color.RED);
int x = carOffset;
int y = 500;
int width=75;
int height=35;
g2D.fillRect(x,y,width,height);
g2D.fillRect(x,y+15,width+20,20);
// Draw Wheels of Car, Black
g2D.setColor(Color.BLACK);
g2D.fillOval(x+15, y+20, 25, 25);
g2D.fillOval(x+50, y+20, 25, 25);
if (carOffset>1000) {
carOffset=0;
}
}
}
StreamImageView class
package application;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import multithreading.ThreadUtil;
import util.GraphicsUtil;
public class StreamImageView {
private static AtomicInteger instanceIdGenerator = new AtomicInteger();
private ConcurrentLinkedQueue<Image> imageQueue = new ConcurrentLinkedQueue<>();
public ConcurrentLinkedQueue<Image> getImageQueue() {
return imageQueue;
}
private ImageView imageView;
public ImageView getImageView() {
return imageView;
}
private String id;
public String getId() {
return id;
}
public StreamImageView() {
this(null);
}
public StreamImageView(Image initialFrame) {
id="streamImageView-"+instanceIdGenerator.getAndIncrement();
if (null == initialFrame) {
BufferedImage bufferedImage = new BufferedImage(1000, 1000, BufferedImage.TYPE_INT_RGB);
Graphics2D g2D = bufferedImage.createGraphics();
g2D.setColor(Color.WHITE);
g2D.fillRect(0, 0, 1000, 1000);
initialFrame = GraphicsUtil.convertBufferedImageToImage(bufferedImage);
}
imageView = new ImageView(initialFrame);
Runnable updateImageView = () -> {
while (true) {
Image image = imageQueue.poll();
if (null != image) {
imageView.setImage(image);
}
}
};
Thread imageViewUpdateConsumer = new Thread(updateImageView);
ThreadUtil.addTask(imageViewUpdateConsumer);
}
}
Utility Class for turning BufferedImage into JavaFX Image
package util;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javafx.scene.image.Image;
public class GraphicsUtil {
private static Logger logger = Logger.getLogger(GraphicsUtil.class.getName());
public static Image convertBufferedImageToImage(BufferedImage bufferedImage) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, "BMP", baos);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Image image = new Image(bais);
return image;
} catch (IOException e) {
logger.severe("Error converting BufferedImage to Image");
return null;
}
}
}
Wired together in main application and set to run on a different thread
package application;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import multithreading.ThreadUtil;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
try {
VBox vbox = new VBox();
Scene scene = new Scene(vbox,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
StreamImageView streamImageView = new StreamImageView();
vbox.getChildren().add(streamImageView.getImageView());
RenderEngine renderEngine = new RenderEngine(streamImageView.getImageQueue());
ThreadUtil.addTask(renderEngine);
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Thread Pool Utility Class
package multithreading;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
public class ThreadUtil {
private static Logger logger = Logger.getLogger(ThreadUtil.class.toString());
private static BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(6);
private static ThreadPoolExecutor threadPoolExecutor;
public static BlockingQueue<Runnable> getWorkQueue() {
if (null == workQueue) {
workQueue = new ArrayBlockingQueue<>(6);
}
return workQueue;
}
public static ThreadPoolExecutor getThreadPool() {
if (null == threadPoolExecutor) {
threadPoolExecutor = new ThreadPoolExecutor(6, 12, 1000, TimeUnit.MILLISECONDS, getWorkQueue());
}
return threadPoolExecutor;
}
public static void addTask(Runnable task) {
getThreadPool().submit(task);
}
/* public static void addVirtualTask(Runnable task) {
getThreadPool().submit(Thread.ofVirtual().start(task));
} */ // Potential update for Java 19
}
There would be an Image here, provided the Video on Twitter
Rain Sounds Help as Encouragement
Added a draw Mountain function and shared on Twitter
public void drawMountain(Graphics2D g2D) {
g2D.setColor(Color.CYAN);
int x[] = { 100, 250, 400 };
int y[] = { 400, 250, 400 };
g2D.fillPolygon(x, y, 3);
}
The car in front of the Mountain needs a story
Free Starter Story Outline to go with
Random THEME – Love conquers all
Random PLOT – Tragedy
Onomatopoeia Metaphor Allegory Flashback Juxtaposition Metaphor Onomatopoeia Flashback Simile Imagery
Random Villain Dragon
Siren Swarm Giant Necromancer Bandit Dragon Mislead Revolutionary Stone Elemental Deranged Sniper Water Elemental Siren Poltergeist Siren Siren Mislead Revolutionary Swarm Psychopath Hostile Alien Spaceship Giant Dragon
Descriptive Writing Amps
- [brilliant, loud, proper] lizard whispered
- [smooth, diminished, resonant] gas giant relayed
- [happy, sweet, rough] demon halted
- [violet, harmonious, blue] ice cream evaded
- [gargantuan, kinetic, loud] moon dashed
- [soft, microscopic, green] earth pulverized
- [sad, delightful, black] castle evoked
- [green, cyan, gargantuan] ruby unfurled
- [colossal, blissful, calm] gold blocked
- [sweet, microscopic, happy] jet magnified
Random Hero – Knight
And But Therefore Event Sets
- [Hero] is attacked by [Monster] but they are delayed therefore they fight the [monster]
- [Hero] is given assistance from [side character] but [monster] challenges [Hero] with hurtful scary words therefore they decide to learn more
- Dramatic Dialogue with subtext but [monster] challenges [Hero] with hurtful scary words therefore they fight the [monster]
- [Hero] is given assistance from [side character] but they get there early therefore they fight the [monster]
- [Hero] gets in jet but they are delayed therefore they seek assistance with [side character]
- diffuse a bomb but it rains therefore they travel to a new area
- There is a solar eclipse but [monster] shows up therefore they seek assistance with [side character]
- [Hero] is given assistance from [side character] but they get there early therefore they travel to a new area
- fight a [monster] but they are delayed therefore they fight the [monster]
- fight a [monster] but it rains therefore they fight the [monster]
The World invests in Tech and Villain Contrast, Obfuscation through Acronyms, seems only wise to invest in some repair
Fault Tolerance is Power that might not always be taught ideally in High Schools
https://en.wikipedia.org/wiki/Fault_tolerance
https://en.wikipedia.org/wiki/Triple_modular_redundancy
Terms I really didn’t learn about until my Masters in Engineering
Ahead of the game is Power, easy to upsell it to less
Sun erosion feature
public void drawSun(Graphics2D g2D) {
g2D.setColor(new Color(0x2222FF));
g2D.fillOval(25, 25, 300, 300);
g2D.setColor(new Color(0x5555FF));
g2D.fillOval(75, 75, 100, 100);
g2D.setColor(Color.YELLOW);
g2D.fillOval(100, 100, 30, 30);
g2D.setColor(new Color(0xFFFFFF));
g2D.fillOval(105, 105, 20, 20);
}
Updated for Flying Cars
Video available on Twitter
package application;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Logger;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import performance.PerformanceReading;
import performance.PerformanceUtil;
import performance.TimeMeasurementUtil;
import util.GraphicsUtil;
public class RenderEngine implements Runnable {
public static Logger logger = Logger.getLogger(RenderEngine.class.getName());
PerformanceUtil performanceUtil = new PerformanceUtil();
private ConcurrentLinkedQueue<Image> concurrentImageQueue;
private ConcurrentLinkedQueue<KeyCode> keyBoardEvent = new ConcurrentLinkedQueue<>();
public ConcurrentLinkedQueue<KeyCode> getKeyBoardEventQueue() {
return keyBoardEvent;
}
public RenderEngine(ConcurrentLinkedQueue<Image> concurrentImageQueue) {
this.concurrentImageQueue = concurrentImageQueue;
}
@Override
public void run() {
renderFrameLoop();
}
public void renderFrameLoop() {
int timeSinceLastPerformanceReading = 0;
int sleepTimeMilliseconds = 15;
while (true) {
performanceUtil.start();
BufferedImage bufferedImage = renderFrame();
Image image = GraphicsUtil.convertBufferedImageToImage(bufferedImage);
concurrentImageQueue.add(image);
performanceUtil.stop();
performanceUtil.addPerformanceReading("Frame Rendered");
// logger.info("Frame rendered");
try {
Thread.sleep(sleepTimeMilliseconds);
while (!keyBoardEvent.isEmpty()) {
KeyCode keyCode = keyBoardEvent.poll();
logger.info("Key pressed: "+keyCode.getName());
if (keyCode.isArrowKey())
{
if (keyCode.getCode()==KeyCode.DOWN.getCode()) {
carVerticalOffset+=3;
}
if (keyCode.getCode()==KeyCode.UP.getCode()) {
carVerticalOffset-=3;
}
}
}
if (timeSinceLastPerformanceReading>5000) {
timeSinceLastPerformanceReading=0;
PerformanceReading reading = performanceUtil.summarizePerformanceReadings(
"Frame Render Time Average",
performanceUtil.getPerformanceReadings());
logger.info("Current render time average nanoseconds: "+reading.getNanoseconds());
int milliseconds = TimeMeasurementUtil.convertNanosecondsToMilliseconds(
reading.getNanoseconds());
logger.info("Current render time average milliseconds: "+milliseconds);
} else {
timeSinceLastPerformanceReading += sleepTimeMilliseconds;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private int carOffset=0;
private int carVerticalOffset=0;
public BufferedImage renderFrame() {
BufferedImage bufferedImage = new BufferedImage(1000, 1000, BufferedImage.TYPE_INT_RGB);
Graphics2D g2D = bufferedImage.createGraphics();
g2D.setColor(Color.BLACK);
g2D.fillRect(0, 0, 1000, 1000);
g2D.setColor(Color.GREEN); // Draw Green Ground Height 400 to 1000
g2D.fillRect(0, 400, 1000, 1000);
g2D.setColor(Color.BLUE); // Draw Blue Sky Height 0 to 400
g2D.fillRect(0, 0, 1000, 400);
drawMountain(g2D);
drawSun(g2D);
drawCar(g2D);
return bufferedImage;
}
public void drawSun(Graphics2D g2D) {
g2D.setColor(new Color(0x2222FF));
g2D.fillOval(25, 25, 300, 300);
g2D.setColor(new Color(0x5555FF));
g2D.fillOval(75, 75, 100, 100);
g2D.setColor(Color.YELLOW);
g2D.fillOval(100, 100, 30, 30);
g2D.setColor(new Color(0xFFFFFF));
g2D.fillOval(105, 105, 20, 20);
}
public void drawMountain(Graphics2D g2D) {
g2D.setColor(Color.CYAN);
int x[] = { 100, 250, 400 };
int y[] = { 400, 250, 400 };
g2D.fillPolygon(x, y, 3);
}
public void drawCar(Graphics2D g2D) {
carOffset+=10;
// Draw Body of Car, RED
g2D.setColor(Color.RED);
int x = carOffset;
int y = 500+carVerticalOffset;
int width=75;
int height=35;
g2D.fillRect(x,y,width,height);
g2D.fillRect(x,y+15,width+20,20);
// Draw Wheels of Car, Black
g2D.setColor(Color.BLACK);
g2D.fillOval(x+15, y+20, 25, 25);
g2D.fillOval(x+50, y+20, 25, 25);
if (carOffset>1000) {
carOffset=0;
}
}
}
package application;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import multithreading.ThreadUtil;
public class Main extends Application {
private static Logger logger = Logger.getLogger(Main.class.getName());
@Override
public void start(Stage primaryStage) {
try {
VBox vbox = new VBox();
Scene scene = new Scene(vbox,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
StreamImageView streamImageView = new StreamImageView();
vbox.getChildren().add(streamImageView.getImageView());
RenderEngine renderEngine = new RenderEngine(streamImageView.getImageQueue());
ThreadUtil.addTask(renderEngine);
scene.setOnKeyPressed((event) -> {
// Process Keyboard Input
KeyCode keyCode = event.getCode();
renderEngine.getKeyBoardEventQueue().add(keyCode);
});
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Free with Glacier Melting Tech
Add in the potential for Processing Buffered Images on Servers
Exascale with a little latency likely can be a lot of power
One server draws the flying car
One server draws the background
Client combines the images together
Comprehension of gains to Load Balancing and Ability to use Heterogeneous Multiprocessing Fully Comprehended?
Faster Home Interconnects?
Potential for Multimachine Frame Processing as Possibility
Phones and Laptops have processors, processors that sit idle while gaming, or are useful for processing frames? Potential could be Power
More Ability to utilizing Processing in Real Ways will become more and more possible as time goes on, getting Companies to enable Heterogenous Multiprocessing like OpenCL is Not a given
https://www.netgear.com/home/wifi/routers/raxe500/
10.8Gbps / 8 bytes/s = 1.35 Gigabytes/second theoretical Max Bandwidth
Some potential for Throughput of off client rendered frames
Thoughts of Pattern Matching used to analyze Runtime Systems like the JRE (Java Runtime Environment) for suggestions to increase Performance
Game Engines analyzed by systems like Cortana and Alexa turned up with the Power of the cloud?
Pipeline gains fully appreciated?
Theoretical Maxes do not always lead to actual Throughput, that said current gains in Technology have potential for much less than Max Throughput to lead to a lot of Power
Testing my PCIE Drive in an Enclosure Writing a 1 GB file got the following results
INFO: Time Required to add and verify one million numbers: 1452 ms
Mar 13, 2023 4:34:11 AM performance.DiskWriteReadPerformance testWritePerformance
INFO: Bytes in String: 1034 mb
Mar 13, 2023 4:34:27 AM file.FileUtil writeFile
INFO: Wrote to G:\testwrite.txt with 1034240000 bytes.
Mar 13, 2023 4:34:27 AM performance.DiskWriteReadPerformance testWritePerformance
INFO: Time Required to write 1034 mb data to G:\testwrite.txt: 15720 ms
Mar 13, 2023 4:34:29 AM performance.DiskWriteReadPerformance testReadPerformance
INFO: Time Required to read 1034 mb data from G:\testwrite.txt: 1760 ms
Mar 13, 2023 4:34:32 AM performance.DiskWriteReadPerformance testWritePerformance
INFO: Bytes in String: 1034 mb
Mar 13, 2023 4:34:35 AM file.FileUtil writeFile
INFO: Wrote to C:\Users\jason\eclipse-workspace\PerformanceTestRunner\testwrite.txt with 1034240000 bytes.
Mar 13, 2023 4:34:35 AM performance.DiskWriteReadPerformance testWritePerformance
INFO: Time Required to write 1034 mb data to C:\Users\jason\eclipse-workspace\PerformanceTestRunner\testwrite.txt: 3340 ms
Mar 13, 2023 4:34:37 AM performance.DiskWriteReadPerformance testReadPerformance
INFO: Time Required to read 1034 mb data from C:\Users\jason\eclipse-workspace\PerformanceTestRunner\testwrite.txt: 1907 ms
G drive is PCIE Samsung 970 in an ASUS external enclosure
C drive is SSD on my MSI GF65 Thin 10UE
I believe it is USB 3.0
16 seconds to write 1 GB, or 8 gigabits, write throughput of 0.5 gigabits/second.
2 seconds to read 1 GB from PCIE drive equals read throughput of 0.5 gigabytes/second, or 0.5*8 = 4 gigabits/second
Routers with Max Throughput of 10.8 Gigabits per second is a lot of Power
Network Speeds rivaling speeds of PCIE drives is pretty impressive, has potential to enable things potentially once thought impossible.
More Network Throughput equals bigger files, bigger files need stronger processors
Free Starter Performance Testing Classes (MIT License)
package performance;
import java.io.IOException;
import java.io.StringWriter;
import java.util.logging.Logger;
import file.FileUtil;
public class DiskWriteReadPerformance {
private static Logger logger = Logger.getLogger(DiskWriteReadPerformance.class.getName());
public static void testWritePerformance(String filename) {
final StringWriter stringWriter = new StringWriter();
for (int i=0; i<10240000; i++) {
stringWriter.append("AAAAAaaaaaBBBBBccccc");
stringWriter.append("CCCCCdddddEEEEEfffff");
stringWriter.append("GGGGGhhhhhIIIIIjjjjj");
stringWriter.append("KKKKKlllllMMMMMnnnnn");
stringWriter.append("OOOOOpppppQQQQQrrrrr\n");
}
int numberOfBytes = stringWriter.toString().getBytes().length;
int megabytes = numberOfBytes/1000000;
logger.info("Bytes in String: "+megabytes+" mb");
PerformanceUtil.getInstance().start();
try {
FileUtil.writeFile(filename, stringWriter.toString());
} catch (IOException e) {
logger.severe(e.getMessage());
}
PerformanceUtil.getInstance().stop();
logger.info("Time Required to write "+megabytes+ " mb data to "+filename+": "
+PerformanceUtil.getInstance().getRecordedTimeMilliseconds()+" ms");
}
public static void testReadPerformance(String filename) {
PerformanceUtil.getInstance().start();
try {
String fileAsString = FileUtil.readFileAsString(filename);
PerformanceUtil.getInstance().stop();
int megabytes = fileAsString.getBytes().length/1000000;
logger.info("Time Required to read "+megabytes+" mb data from "+filename+": "
+PerformanceUtil.getInstance().getRecordedTimeMilliseconds()+" ms");
} catch (IOException e) {
logger.severe(e.getMessage());
}
}
}
package performance;
import java.util.ArrayList;
public class PerformanceUtil {
private ArrayList<PerformanceReading> performanceReadings = new ArrayList<>();
private boolean stopWatchStarted = false;
private long startTime;
private long endTime;
private long recordedTime=0;
private static PerformanceUtil instance;
public static PerformanceUtil getInstance() {
if (null == instance) {
instance = new PerformanceUtil();
}
return instance;
}
public boolean start() {
if (stopWatchStarted) {
return false;
} else {
startTime=System.nanoTime();
stopWatchStarted=true;
return true;
}
}
public boolean stop() {
if (!stopWatchStarted) {
return false;
} else {
endTime=System.nanoTime();
stopWatchStarted=false;
return true;
}
}
public long getRunningTime() {
long runningTime = System.nanoTime()-startTime;
return runningTime;
}
public long getRecordedTime() {
recordedTime=endTime-startTime;
return recordedTime;
}
public int getRecordedTimeMilliseconds() {
recordedTime=endTime-startTime;
double milliseconds = recordedTime/1000000;
return (int) milliseconds;
}
public void clearPerformanceReadingsList() {
performanceReadings = new ArrayList<>();
}
public void addPerformanceReading(String performanceReadingName) {
long recordedTime = getRecordedTime();
PerformanceReading performanceReading = new PerformanceReading(recordedTime, performanceReadingName);
performanceReadings.add(performanceReading);
}
public PerformanceReading summarizePerformanceReadings(String readingName, ArrayList<PerformanceReading> readings) {
long sum=0;
for (int i=0; i<readings.size(); i++) {
sum = readings.get(i).getNanoseconds();
}
long average = sum/readings.size();
return new PerformanceReading(average, readingName);
}
public ArrayList<PerformanceReading> getPerformanceReadings() {
return performanceReadings;
}
}
package performance;
public class PerformanceReading {
public PerformanceReading(final long nanoseconds, final String name) {
this.nanoseconds = nanoseconds;
this.name = name;
}
private long nanoseconds;
private String name;
public long getNanoseconds() {
return nanoseconds;
}
public String getName() {
return name;
}
}