Finished MPROM support. Fixes #10
[Mograsim.git] / plugins / net.mograsim.plugin.core / src / net / mograsim / plugin / launch / MainMemoryBlockExtension.java
index ff633fe..16c6db0 100644 (file)
@@ -1,5 +1,7 @@
 package net.mograsim.plugin.launch;
 
+import static net.mograsim.plugin.preferences.PluginPreferences.MAX_MEMORY_CHANGE_INTERVAL;
+
 import java.math.BigInteger;
 import java.util.HashSet;
 import java.util.Set;
@@ -24,7 +26,8 @@ import net.mograsim.plugin.MograsimActivator;
 
 public class MainMemoryBlockExtension extends PlatformObject implements IMemoryBlockExtension
 {
-       // TODO do we want to make the memory accessible byte-wise?
+       private static final byte MEM_BYTE_FLAGS = (byte) (MemoryByte.READABLE | MemoryByte.WRITABLE | MemoryByte.ENDIANESS_KNOWN
+                       | MemoryByte.BIG_ENDIAN);
 
        private final String expression;
        private final MachineDebugTarget debugTarget;
@@ -44,6 +47,12 @@ public class MainMemoryBlockExtension extends PlatformObject implements IMemoryB
        private final MemoryCellModifiedListener memListener;
        private final AtomicBoolean memListenerRegistered;
 
+       private final int maxContentChangeInterval;
+       private final Object contentChangeLock;
+       private Thread contentChangeThread;
+       private long nextContentChangeAllowedMillis;
+       private boolean contentChangeQueued;
+
        public MainMemoryBlockExtension(MachineDebugTarget debugTarget, String expression, @SuppressWarnings("unused") Object expressionContext)
                        throws DebugException
        {
@@ -64,13 +73,18 @@ public class MainMemoryBlockExtension extends PlatformObject implements IMemoryB
 
                if (baseAddrWords.compareTo(minAddrWords) < 0 || baseAddrWords.compareTo(maxAddrWords) > 0)
                        throwDebugException("Base address out of range");
-               if (baseAddrWords.add(lengthWords).compareTo(maxAddrWords) > 0)
+               if (baseAddrWords.add(lengthWords).subtract(BigInteger.ONE).compareTo(maxAddrWords) > 0)
                        throwDebugException("End address out of range");
 
                this.clients = new HashSet<>();
                // don't check whether the address is in range, because this memory block could be read outside its "range"
-               this.memListener = a -> fireContentChangeEvent();
+               this.memListener = a -> queueFireContentChangeEvent();
                this.memListenerRegistered = new AtomicBoolean();
+
+               // TODO add a listener
+               this.maxContentChangeInterval = MograsimActivator.instance().getPluginPrefs().getInt(MAX_MEMORY_CHANGE_INTERVAL);
+               this.contentChangeLock = new Object();
+               this.nextContentChangeAllowedMillis = System.currentTimeMillis() - maxContentChangeInterval - 1;
        }
 
        @Override
@@ -183,7 +197,7 @@ public class MainMemoryBlockExtension extends PlatformObject implements IMemoryB
        @Override
        public int getAddressSize() throws DebugException
        {
-               return Long.BYTES;
+               return (getBigLength().bitLength() + 7) / 8;
        }
 
        @Override
@@ -231,9 +245,9 @@ public class MainMemoryBlockExtension extends PlatformObject implements IMemoryB
                                int l = wordBytes[0] == 0 ? 1 : 0;
                                int k;
                                for (k = 0; k < cellWidthBytes - wordBytes.length + l; k++)
-                                       bytes[i + k] = new MemoryByte();
+                                       bytes[i + k] = new MemoryByte((byte) 0, MEM_BYTE_FLAGS);
                                for (; k < cellWidthBytes; k++, l++)
-                                       bytes[i + k] = new MemoryByte(wordBytes[l]);
+                                       bytes[i + k] = new MemoryByte(wordBytes[l], MEM_BYTE_FLAGS);
                        } else
                                for (int k = 0; k < cellWidthBytes; k++)
                                        bytes[i + k] = new MemoryByte((byte) 0, (byte) 0);
@@ -265,7 +279,7 @@ public class MainMemoryBlockExtension extends PlatformObject implements IMemoryB
 
                if (!clients.isEmpty())
                        registerMemoryListener();
-               fireContentChangeEvent();
+               queueFireContentChangeEvent();
        }
 
        @Override
@@ -302,6 +316,24 @@ public class MainMemoryBlockExtension extends PlatformObject implements IMemoryB
        {
                if (memListenerRegistered.getAndSet(false))
                        mem.deregisterCellModifiedListener(memListener);
+
+               Thread contentChangeThreadLocal;
+               synchronized (contentChangeLock)
+               {
+                       contentChangeThreadLocal = contentChangeThread;
+                       // set contentChangeQueued here to prevent the following scenario:
+                       // 1. A change event is requested -> it gets fired "directly"
+                       // 2. A second change event is requested during the "cooldown time" -> a queue thread gets started
+                       // 3. The last client is disconnected -> the queue thread is interrupted
+                       // 4. A new client is connected
+                       // 5. A third change event is requested; queueFireContentChangeEvent locks contentChangeLock
+                       // before the queue thread locks contentChangeLock.
+                       // Now queueFireContentChangeEvent would return doing nothing, since contentChangeQueued still is true,
+                       // causing a change event to be missed.
+                       contentChangeQueued = false;
+               }
+               if (contentChangeThreadLocal != null)
+                       contentChangeThreadLocal.interrupt();
        }
 
        @Override
@@ -323,10 +355,62 @@ public class MainMemoryBlockExtension extends PlatformObject implements IMemoryB
                return cellWidthBytes;
        }
 
+       private void queueFireContentChangeEvent()
+       {
+               long sleepTime;
+               boolean fireInOwnThread = false;
+               synchronized (contentChangeLock)
+               {
+                       if (contentChangeQueued)
+                               return;
+                       long nextContentChangeAllowedMillisLocal = nextContentChangeAllowedMillis;
+                       long now = System.currentTimeMillis();
+                       sleepTime = nextContentChangeAllowedMillisLocal - now;
+                       if (sleepTime >= 0)
+                       {
+                               fireInOwnThread = true;
+                               contentChangeQueued = true;
+                               nextContentChangeAllowedMillis = nextContentChangeAllowedMillisLocal + maxContentChangeInterval;
+                       } else
+                       {
+                               fireInOwnThread = false;
+                               nextContentChangeAllowedMillis = now + maxContentChangeInterval;
+                       }
+               }
+               if (fireInOwnThread)
+               {
+                       // the following two statements can't cause racing problems since we set contentChangeQueued to true,
+                       // which means no-one will write this field until the thread started:
+                       // this method will never get here, and in this moment there is no (other) content change thread running,
+                       // since contentChangeQueued was false
+                       contentChangeThread = new Thread(() ->
+                       {
+                               boolean interrupted = false;
+                               try
+                               {
+                                       Thread.sleep(sleepTime);
+                               }
+                               catch (@SuppressWarnings("unused") InterruptedException e)
+                               {
+                                       interrupted = true;
+                               }
+                               synchronized (contentChangeLock)
+                               {
+                                       contentChangeThread = null;
+                                       contentChangeQueued = false;
+                               }
+                               if (!interrupted && !Thread.interrupted())
+                                       fireContentChangeEventNow();
+                       });
+                       contentChangeThread.start();
+               } else
+                       fireContentChangeEventNow();
+       }
+
        /**
         * Fires a terminate event for this debug element.
         */
-       private void fireContentChangeEvent()
+       private void fireContentChangeEventNow()
        {
                fireEvent(new DebugEvent(this, DebugEvent.CHANGE, DebugEvent.CONTENT));
        }