Skip to content
Open
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,13 @@ JLS
===

Java Logic Simulator

## Compiling

### Windows 10

With JDK installed:
```shell
javac -d ./bin -cp ./lib/jhall.jar ./src/jls/*.java ./src/jls/edit/*.java ./src/jls/elem/*.java ./src/jls/sim/*.java ./xz/*.java ./xz/org/tukaani/xz/*.java ./xz/org/tukaani/xz/check/*.java ./xz/org/tukaani/xz/common/*.java ./xz/org/tukaani/xz/delta/*.java ./xz/org/tukaani/xz/index/*.java ./xz/org/tukaani/xz/lz/*.java ./xz/org/tukaani/xz/lzma/*.java ./xz/org/tukaani/xz/rangecoder/*.java ./xz/org/tukaani/xz/simple/*.java
java -cp ./bin/ jls.JLS
```
169 changes: 94 additions & 75 deletions src/jls/edit/SimpleEditor.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
Expand Down Expand Up @@ -2821,18 +2822,38 @@ private boolean overlap() {
}*/


// Set is O(n log(n)) to traverse over and doesn't benefit from cache locality.
// we're doing a lot of iterating over the same collection here,
// so it makes sense to use a temporary array for cache locality and O(n) traversal.
Element[] selectedArr = selected.toArray(Element[]::new);
ArrayList<Element> elementsArr = new ArrayList(circuit.getElements());
// elementsArr.removeAll(selected);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These top level functions often have significant hidden optimizations to them. I'd benchmark before doing this and claiming this is the part that improves performance.

{
int i = 0;
while (i < elementsArr.size()) {
if (selected.contains(elementsArr.get(i))) {
// "swap" remove - slightly cheaper because we don't care about order
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're very worried about performance for this operation, a linked list has better cache locality.

Copy link
Copy Markdown
Author

@AmityWilder AmityWilder Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused how that can be true...

An array has all of its elements next to each other in memory, so the entire surrounding chunk of memory can be pulled into memory at once and contain those adjacent elements.

A linked list has pointers to adjacent elements, meaning it couldn't be cached without dereferencing those pointers--which in addition to being extra operations (and slow ones at that: loading data from RAM) may be unsafe to do speculatively since the CPU can't be certain those pointers belong to the program, or are even valid memory addresses.

Am I incorrect?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'd be almost right if this were C. Less so for Java. In a traditional linked list and doubly linked list, each node has one to three pointers. One is for the next node, optionally for the previous node, and the value is either embedded or has a pointer to it. However, in optimized implementations these nodes are kept densely packed in an array or something more exotic. So, you're comparing something called a long jump to overcome 2+GB changes in virtual address or physical address (we're skipping the kernel considerations for now) or if correctly optimized in some circumstances a standard jump. Those jumps are, practically, cache-busters. Intel has some fancy stuff with their pre-fetcher, but relying on that from a programmer's perspective is faulty at best, meaning that you count on missing L1, L2, L3 (and sometimes L4) cache, hitting RAM, and killing the performance. Now, when working with swapping to the end of a dense array, you're busting this cache on every swap just by virtue of touching the end of the array while you were just working on the beginning. However, with Java's presumably optimized implementation, these removals are always touching bytes which are within one or two words away. This means that the cache characteristics are great. So even if you're performing 5X the operations, the fact this the CPU assumes that you're working with memory right next to each other allows for simpler algorithms to pre-fetch that memory and avoids the cache misses in the first place. And this is without the extremely optimized data structures which are actually probably being used which are going to have better characteristics. For these reasons, trust the standard library. Neither of us are going to out-smart it.

Once you hit system organization, operating systems, and compilers you'll formally be introduced to these topics if your profs are good.

One tip for compilers -- learn how to use stack machines. They aren't always covered but they allow you to entirely skip register management and it makes your life immensely easier.

Copy link
Copy Markdown
Author

@AmityWilder AmityWilder Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see! I had completely not realized that accessing the end would be what causes the slowdown (and I had NO idea about any of those linked list optimizations), but it makes perfect sense after you explained it!

I'll try benchmarking this and an alternative linked list implementation and keep whichever is faster. However, I'm not entirely sure how to benchmark this code without its speed being influenced by the speed of my own movements. As far as I can tell, there isn't a good way of isolating the overlap code and running it with a variety specific circuits--I would have to trigger the overlap method manually. Do you have any suggestions?

elementsArr.set(i, elementsArr.get(elementsArr.size() - 1));
elementsArr.remove(elementsArr.size() - 1); // pop
// don't increment on erase because [i] now refers to a new element
}
else {
i++;
}
}
}

// check every element in the selected set
for (Element sel : selected) {
for (Element sel : selectedArr) {

// check against every element in the circuit
for (Element el : circuit.getElements()) {
boolean anyIntersection = false;

// ignore elements in the selected set
if (selected.contains(el))
continue;
// check against every (unselected, bounds-overlapping) element in the circuit
for (Element el : elementsArr) {

// check simple overlap of areas
if (sel.intersects(el)) {
anyIntersection = true;

// no overlap if possible connection,
boolean ok = false;
Expand Down Expand Up @@ -2910,42 +2931,42 @@ else if (el instanceof Wire) {
// selected is not a wire end
else {

// put to wire end
for (Put put : sel.getAllPuts()) {

// if not a wire end, ignore
if (!(el instanceof WireEnd))
continue;
// if element is a wire end, ...
if (el instanceof WireEnd) {
WireEnd end = (WireEnd)el;

// if don't line up, ignore
if (put.getX() != end.getX() || put.getY() != end.getY()) {
continue;
}
// put to wire end
for (Put put : sel.getAllPuts()) {

// if already attached to this wire end, ignore
if (end == put.getWireEnd()) {
ok = true;
continue;
}
// if don't line up, ignore
if (put.getX() != end.getX() || put.getY() != end.getY()) {
continue;
}

// if attached through a single wire, ignore
WireEnd putEnd = put.getWireEnd();
if (putEnd != null &&
putEnd.getOnlyWire().getOtherEnd(putEnd) == end) {
ok = true;
continue;
}
// if already attached to this wire end, ignore
if (end == put.getWireEnd()) {
ok = true;
continue;
}

// if cannot connect, return
if (!canConnect(end,put)) {
untouchAll();
return true;
}
// if attached through a single wire, ignore
WireEnd putEnd = put.getWireEnd();
if (putEnd != null &&
putEnd.getOnlyWire().getOtherEnd(putEnd) == end) {
ok = true;
continue;
}

end.setTouching(true);
put.setTouching(true);
ok = true;
// if cannot connect, return
if (!canConnect(end,put)) {
untouchAll();
return true;
}

end.setTouching(true);
put.setTouching(true);
ok = true;
}
}
}
if (!ok) {
Expand All @@ -2954,52 +2975,50 @@ else if (el instanceof Wire) {
return true;
}
}
}

// no intersection, but wires may be overlapping wire ends
// or puts might line up
else {
// no intersection, but wires may be overlapping wire ends
// or puts might line up
if (!anyIntersection) {

// see if wires connected to a wire end dragged onto wire ends
if (sel instanceof WireEnd) {
WireEnd end = (WireEnd)sel;
for (Wire wire : end.getWires()) {
for (Element elm : circuit.getElements()) {
if (sel == elm)
continue;
if (!(elm instanceof WireEnd)) {
continue;
}
WireEnd otherEnd = (WireEnd)elm;
if (wire.touches(otherEnd)) {
overlapMessage = "overlap";
untouchAll();
return true;
}
// see if wires connected to a wire end dragged onto wire ends
if (sel instanceof WireEnd) {
WireEnd end = (WireEnd)sel;
for (Wire wire : end.getWires()) {
for (Element el : elementsArr) {
if (!(el instanceof WireEnd)) {
continue;
}
WireEnd otherEnd = (WireEnd)el;
if (wire.touches(otherEnd)) {
overlapMessage = "overlap";
untouchAll();
return true;
}
}
}
}

// see if wires connected to puts dragged onto wire ends
for (Put p : sel.getAllPuts()) {
if (p.isAttached()) {
Wire wire = p.getWireEnd().getOnlyWire();
for (Element elm : circuit.getElements()) {
if (sel == elm)
continue;
if (!(elm instanceof WireEnd)) {
continue;
}
WireEnd otherEnd = (WireEnd)elm;
if (wire.touches(otherEnd)) {
overlapMessage = "overlap";
untouchAll();
return true;
}
// see if wires connected to puts dragged onto wire ends
for (Put p : sel.getAllPuts()) {
if (p.isAttached()) {
Wire wire = p.getWireEnd().getOnlyWire();
for (Element el : elementsArr) {
if (!(el instanceof WireEnd)) {
continue;
}
WireEnd otherEnd = (WireEnd)el;
if (wire.touches(otherEnd)) {
overlapMessage = "overlap";
untouchAll();
return true;
}
}
}
}

// check all put combinations
// check all put combinations
for (Element el : elementsArr) {
for (Put p1 : sel.getAllPuts()) {
for (Put p2 : el.getAllPuts()) {

Expand All @@ -3011,7 +3030,7 @@ else if (el instanceof Wire) {
// ignore overlaps on already connected puts
WireEnd end1 = p1.getWireEnd();
WireEnd end2 = p2.getWireEnd();
if (end1 != null && end2 != null &&
if (end1 != null && end2 != null &&
end1.getOnlyWire().getOtherEnd(end1) == end2) {
continue;
}
Expand All @@ -3030,8 +3049,8 @@ else if (el instanceof Wire) {
}
}
}
repaint();
return false;
repaint();
return false;
} // end of overlap method

/**
Expand Down
33 changes: 33 additions & 0 deletions src/jls/elem/Element.java
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,26 @@ public int getY() {

return y;
} // end of getY method

/**
* Get width of this element.
*
* @return the width.
*/
public int getWidth() {

return width;
} // end of getWidth method

/**
* Get height of this element.
*
* @return the height.
*/
public int getHeight() {

return height;
} // end of getHeight method

/**
* Get the trace position of this element.
Expand Down Expand Up @@ -333,6 +353,19 @@ public boolean isInside(Rectangle rect) {
return rect.contains(me);
} // end of isInside method

/**
* See if this element is intersecting a given rectangle.
*
* @param rect The given rectangle.
*
* @return true if the element is intersecting, false if not.
*/
public boolean isOverlapping(Rectangle rect) {

Rectangle me = getRect();
return rect.intersects(me);
} // end of isOverlapping method

/**
* Set/reset highlight.
*
Expand Down