diff --git a/SpriteCompiler/AI/InstrumentedNodeExpander.cs b/SpriteCompiler/AI/InstrumentedNodeExpander.cs
new file mode 100644
index 0000000..675c806
--- /dev/null
+++ b/SpriteCompiler/AI/InstrumentedNodeExpander.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SpriteCompiler.AI
+{
+ public static class IntrumentedParameters
+ {
+ public const string NODES_EXPANDED = "nodesExpanded";
+ }
+
+ public class InstrumentedNodeExpander : NodeExpanderDelegator
+ where T : ISearchNode
+ where C : ICost
+ {
+ private readonly IDictionary metrics = new Dictionary();
+ private readonly INodeExpander expander;
+
+ public InstrumentedNodeExpander(INodeExpander expander)
+ : base(expander)
+ {
+ ClearMetrics();
+ }
+
+ public override IEnumerable Expand(ISearchProblem problem, T node)
+ {
+ metrics[IntrumentedParameters.NODES_EXPANDED] += 1;
+ return base.Expand(problem, node);
+ }
+
+ public int this[string key]
+ {
+ get
+ {
+ return metrics[key];
+ }
+ }
+
+ public void ClearMetrics()
+ {
+ metrics[IntrumentedParameters.NODES_EXPANDED] = 0;
+ }
+ }
+
+}
diff --git a/SpriteCompiler/Helpers/EnumerableExtensions.cs b/SpriteCompiler/Helpers/EnumerableExtensions.cs
new file mode 100644
index 0000000..3ccd5ab
--- /dev/null
+++ b/SpriteCompiler/Helpers/EnumerableExtensions.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SpriteCompiler.Helpers
+{
+ public static class EnumerableExtensions
+ {
+ public static IEnumerable> Pairs(this IEnumerable source)
+ {
+ return source.Zip(source.Skip(1), (x, y) => Tuple.Create(x, y));
+ }
+
+ public static IEnumerable> GroupAdjacent(this IEnumerable source, Func comparer)
+ {
+ return GroupAdjacent(source, x => x, comparer);
+ }
+
+ public static IEnumerable> GroupAdjacent(this IEnumerable source, Func keySelector)
+ {
+ return GroupAdjacent(source, keySelector, (a, b) => a.Equals(b));
+ }
+
+ public static IEnumerable> GroupAdjacent(this IEnumerable source, Func keySelector, Func comparer)
+ {
+ TKey last = default(TKey);
+ bool haveLast = false;
+ List list = new List();
+ foreach (TSource s in source)
+ {
+ TKey k = keySelector(s);
+ if (haveLast)
+ {
+ if (!comparer(last, k))
+ {
+ yield return new GroupOfAdjacent(list, last);
+ list = new List();
+ list.Add(s);
+ last = k;
+ }
+ else
+ {
+ list.Add(s);
+ last = k;
+ }
+ }
+ else
+ {
+ list.Add(s);
+ last = k;
+ haveLast = true;
+ }
+ }
+ if (haveLast)
+ {
+ yield return new GroupOfAdjacent(list, last);
+ }
+ }
+ }
+
+ public class GroupOfAdjacent : IEnumerable, IGrouping
+ {
+ public TKey Key { get; set; }
+ private List GroupList { get; set; }
+
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+ {
+ return ((System.Collections.Generic.IEnumerable)this).GetEnumerator();
+ }
+
+ System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator()
+ {
+ foreach (var s in GroupList)
+ yield return s;
+ }
+
+ public GroupOfAdjacent(List source, TKey key)
+ {
+ GroupList = source;
+ Key = key;
+ }
+ }
+}
diff --git a/SpriteCompiler/Problem/CodeSequence.cs b/SpriteCompiler/Problem/CodeSequence.cs
index 1d6a72d..a4857ce 100644
--- a/SpriteCompiler/Problem/CodeSequence.cs
+++ b/SpriteCompiler/Problem/CodeSequence.cs
@@ -102,7 +102,11 @@ namespace SpriteCompiler.Problem
public override SpriteGeneratorState Apply(SpriteGeneratorState state)
{
- return state.Clone(_ => _.P &= 0xEF);
+ return state.Clone(_ =>
+ {
+ _.P &= 0xEF;
+ _.AllowModeChange = false;
+ });
}
public override string ToString()
@@ -122,7 +126,11 @@ namespace SpriteCompiler.Problem
public override SpriteGeneratorState Apply(SpriteGeneratorState state)
{
- return state.Clone(_ => _.P |= 0x10);
+ return state.Clone(_ =>
+ {
+ _.P |= 0x10;
+ _.AllowModeChange = false;
+ });
}
public override string ToString()
@@ -224,6 +232,40 @@ namespace SpriteCompiler.Problem
}
}
+ public sealed class STACK_REL_8_BIT_READ_MODIFY_PUSH : CodeSequence
+ {
+ private readonly byte value;
+ private readonly byte mask;
+
+ public STACK_REL_8_BIT_READ_MODIFY_PUSH(byte value, byte mask) : base(11) { this.value = value; this.mask = mask; }
+
+ public override SpriteGeneratorState Apply(SpriteGeneratorState state)
+ {
+ return state.Clone(_ =>
+ {
+ _.A = Register.UNINITIALIZED;
+ _.RemoveByte((ushort)_.S.Value);
+ _.S.Add(-1);
+ });
+ }
+
+ public override string ToString()
+ {
+ return "LDA 0,s / AND #$" + mask.ToString("X2") + " / ORA #$" + value.ToString("X2") + " / PHA";
+ }
+
+ public override string Emit()
+ {
+ return String.Join("\n",
+ FormatLine("", "LDA", "0,s", "4 cycles"),
+ FormatLine("", "AND", "#$" + mask.ToString("X2"), "2 cycles"),
+ FormatLine("", "ORA", "#$" + value.ToString("X2"), "2 cycles"),
+ FormatLine("", "PHA", "", "3 cycles")
+ );
+ }
+ }
+
+
public sealed class STACK_REL_16_BIT_STORE : CodeSequence
{
private readonly byte offset;
diff --git a/SpriteCompiler/Problem/SpriteGeneratorHeuristicFunction.cs b/SpriteCompiler/Problem/SpriteGeneratorHeuristicFunction.cs
index e28d407..e38cf8e 100644
--- a/SpriteCompiler/Problem/SpriteGeneratorHeuristicFunction.cs
+++ b/SpriteCompiler/Problem/SpriteGeneratorHeuristicFunction.cs
@@ -1,8 +1,10 @@
namespace SpriteCompiler.Problem
{
using SpriteCompiler.AI;
+ using SpriteCompiler.Helpers;
using System.Linq;
using System;
+ using System.Collections.Generic;
public sealed class SpriteGeneratorHeuristicFunction : IHeuristicFunction
{
@@ -39,6 +41,29 @@
return gapCost + spanCost;
}
+ private const int MASKED_DATA = 0;
+ private const int SOLID_DATA = 1;
+
+ private int ClassifyChunk(IEnumerable chunk)
+ {
+ return (chunk.First().Mask == 0x00) ? SOLID_DATA : MASKED_DATA;
+ }
+
+ private bool AreEquivalent(SpriteByte left, SpriteByte right)
+ {
+ // Return true if the two bytes are in an equivalence class
+ return
+ // The have to be adjacent
+ ((right.Offset - left.Offset) == 1) &&
+ (
+ // First case: Both bytes are solid
+ ((left.Mask == 0x00) && (right.Mask == 0x00)) ||
+
+ // Second case: Both bytes are masked
+ ((left.Mask != 0x00) && (right.Mask != 0x00))
+ );
+ }
+
public IntegerCost Eval(SpriteGeneratorState state)
{
// An admissible heuistic that calculates a cost based on the gaps and runs in a sprite
@@ -46,40 +71,62 @@
// An even-length run can be done, at best in 4 cycles/word
// An odd-length run is even + 3 cycles/byte
- if (state.IsEmpty) return 0;
-
- var offsets = state.RemainingBytes();
- var start = offsets[0];
- var stack = state.S.Value;
- var curr = start;
- var cost = 0;
-
- for (int i = 1; i < offsets.Count; i++)
+ // Easy case -- no data means no code
+ if (state.IsEmpty)
{
- var prev = curr;
- curr = offsets[i];
-
- if (prev == (curr - 1))
- {
- continue;
- }
-
- // Calculate the estimate cost
- if (state.S.IsScreenOffset)
- {
- cost += SpanAndGapCost(stack, start, prev, curr);
- }
- else
- {
- cost += SpanAndGapCost(start, prev, curr);
- }
-
- // Start a new sppan
- start = curr;
+ return 0;
}
- // End with the span
- cost += SpanAndGapCost(start, curr, curr);
+ // Get a list of all the bytes that have not been emitted
+ var remaining = state.RemainingBytes();
+
+ // Count the number of
+ //
+ // solid words
+ // solid bytes
+ // masked words
+ // masked bytes
+ //
+ // By grouping the remaining bytes into solid / masked runs
+ var chunks = remaining.GroupAdjacent((x, y) => AreEquivalent(x, y));
+
+ // Figure out the full range of offsets that need to be written
+ var range = remaining.Last().Offset - remaining.First().Offset;
+
+ // At best, we can write 257 bytes before needing to move the stack (0,s to 16-bit save to ff,s).
+ var minStackMoves = range / 257;
+
+ // Calculate a heuristic
+ var cost = 0;
+
+ // If the stack is undefined, then there is at least a 2 cycle
+ // cost to initialize it
+ if (!state.S.IsScreenOffset)
+ {
+ cost += 2;
+ }
+
+ cost += minStackMoves * 5;
+
+ // Iterate through each chunk, determine which equivalence class the chunk is
+ // in and then assign a minimum score
+ foreach (var chunk in chunks)
+ {
+ var len = chunk.Count();
+
+ switch (ClassifyChunk(chunk))
+ {
+ // Solid data costs at least 4 cycles / word + 3 cycles for a byte
+ case SOLID_DATA:
+ cost += 4 * (len / 2) + 3 * (len % 2);
+ break;
+
+ // Masked data costs at least 16 cycles / word + 11 cycles for a byte
+ case MASKED_DATA:
+ cost += 16 * (len / 2) + 11 * (len % 2);
+ break;
+ }
+ }
return cost;
}
diff --git a/SpriteCompiler/Problem/SpriteGeneratorState.cs b/SpriteCompiler/Problem/SpriteGeneratorState.cs
index 18cf727..3b0cc3b 100644
--- a/SpriteCompiler/Problem/SpriteGeneratorState.cs
+++ b/SpriteCompiler/Problem/SpriteGeneratorState.cs
@@ -10,11 +10,31 @@
public static List DATASET = null;
public static IDictionary DATASET_BY_OFFSET = null;
+ // Histogram of the byte data
+ public static IDictionary DATASET_SOLID_BYTES = null;
+
+ // Histogram of all possible words -- includes overlaps, e.g. $11 $11 $11 $11 = ($1111, 3)
+ public static IDictionary DATASET_SOLID_WORDS = null;
+
public static SpriteGeneratorState Init(IEnumerable bytes)
{
- DATASET = bytes.ToList();
+ DATASET = bytes.OrderBy(x => x.Offset).ToList();
DATASET_BY_OFFSET = DATASET.ToDictionary(x => (int)x.Offset, x => x);
+ DATASET_SOLID_BYTES = DATASET
+ .Where(x => x.Mask == 0x00)
+ .GroupBy(x => x.Data)
+ .ToDictionary(x => x.Key, x => x.Count())
+ ;
+
+ DATASET_SOLID_WORDS = DATASET
+ .Zip(DATASET.Skip(1), (x, y) => new { Left = x, Right = y })
+ .Where(x => (x.Left.Offset == (x.Right.Offset - 1)) && x.Left.Mask == 0x00 && x.Right.Mask == 0x00)
+ .Select(x => (x.Right.Data << 8) | x.Left.Data)
+ .GroupBy(x => x)
+ .ToDictionary(x => (ushort)x.Key, x => x.Count())
+ ;
+
return new SpriteGeneratorState();
}
@@ -44,6 +64,8 @@
S = Register.UNINITIALIZED;
P = 0x30; // Start in native mode (16 bit A/X/Y) with the carry clear
+
+ AllowModeChange = true;
}
private SpriteGeneratorState(SpriteGeneratorState other)
@@ -55,6 +77,7 @@
D = other.D;
S = other.S;
P = other.P;
+ AllowModeChange = other.AllowModeChange;
}
public override string ToString()
@@ -66,18 +89,19 @@
{
Closed.Add(offset);
Closed.Add((ushort)(offset + 1));
+ AllowModeChange = true;
}
public void RemoveByte(ushort offset)
{
Closed.Add(offset);
+ AllowModeChange = true;
}
- public List RemainingBytes()
+ public List RemainingBytes()
{
return DATASET
- .Select(x => x.Offset)
- .Where(x => !Closed.Contains(x))
+ .Where(x => !Closed.Contains(x.Offset))
.ToList();
}
@@ -112,6 +136,12 @@
public byte P { get; set; }
+ // Flag that is cleared whenever there is a switch from
+ // 8/16-bit mode. It is reset once a PHA or STA occurs.
+ // A PEA instruction has no effect. This gates allowable
+ // state transition to prevent long REP/SEP seqences.
+ public bool AllowModeChange { get; set; }
+
public const byte LONG_A = 0x10;
public const byte LONG_I = 0x20;
@@ -129,7 +159,8 @@
Y.Equals(other.Y) &&
D.Equals(other.D) &&
S.Equals(other.S) &&
- P.Equals(other.P)
+ P.Equals(other.P) &&
+ AllowModeChange == other.AllowModeChange
;
}
@@ -141,7 +172,8 @@
Y.GetHashCode() +
D.GetHashCode() +
S.GetHashCode() +
- P.GetHashCode()
+ P.GetHashCode() +
+ AllowModeChange.GetHashCode()
;
}
diff --git a/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs b/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs
index 97b1b76..3d9e701 100644
--- a/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs
+++ b/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs
@@ -1,6 +1,7 @@
namespace SpriteCompiler.Problem
{
using SpriteCompiler.AI;
+ using SpriteCompiler.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -77,168 +78,120 @@
{
public IEnumerable> Successors(SpriteGeneratorState state)
{
- // This is the work-horse of the compiler. For a given state we need to enumerate all of the
- // potential next operations.
- //
- // 1. If there are 16-bits of data at then current offset, we can
- // a. Use one of the cached valued in A/X/Y/D if they match (4 cycles)
- // b. Use a PEA to push immediate values (5 cycles)
- // c. Load a value into A/X/Y and then push (7 cycles, only feasible if the value appears elsewhere in the sprite)
- // d. Load the value into D and then push (9 cycles, and leaves A = D)
- //
- // 2. Move the stack
- // a. Add a value directly (7 cycles, A = unknown)
- // b. Skip 1 byte (6 cycles, A = unknown TSC/DEC/TSC)
- // c. Multiple skips (LDA X,s/AND/ORA/STA = 16/byte, ADC #/TCS/LDX #/PHX = 10/byte
- //
- // 3. Single-byte at the end of a solid run
- // a. If no registers are 8-bit, LDA #Imm/STA 0,s (8 cycles, sets Acc)
- // b. If any reg is already 8-bit, LDA #imm/PHA (6 cycles)
- //
- // We always try to return actions that write data since that moves us toward the goal state
-
- // Get the list of remaining bytes by removing the closed list from the global sprite
- var open = SpriteGeneratorState.DATASET.Where(x => !state.Closed.Contains(x.Offset)).ToList();
- var bytes = open.ToDictionary(x => x.Offset, x => x);
+ // Get the list of remaining bytes by removing the closed list from the global sprite dataset
+ var open = state.RemainingBytes();
+ // If the open list is empty, there can be only one reaons -- we're in an 8-bit
+ // mode.
+ if (!open.Any())
+ {
+ yield return state.Apply(new LONG_M());
+ yield break;
+ }
+
+ // Get the first byte -- if we are expanding a state we can safely assume there
+ // is still data to expand.
+ var firstByte = open.First();
+
+ // Identify the first run of solid bytes
+ var firstSolid = FirstSolidRun(open);
+
+ // In an initial state, the stack has not been set, but the accumulator contains a screen
+ // offset address. There are three possible options
+ //
+ // 1. Set the stack to the accumulator value. This is optimal when there are very few
+ // data bytes to set and the overhead of moving the stack negates any benefit from
+ // using stack push instructions
+ //
+ // 2. Set the stack to the first offset of the open set. This can be optimal if there are a few
+ // bytes and moving the stack forward a bit can allow the code to reach them without needing
+ // a second stack adjustment.
+ //
+ // 3. Set the stack to the first, right-most offset that end a sequence of solid bytes
+ if (!state.S.IsScreenOffset && state.A.IsScreenOffset)
+ {
+ // If the first byte is within 255 bytes of the accumulator, propose setting
+ // the stack to the accumulator value
+ var delta = firstByte.Offset - state.A.Value;
+ if (delta >= 0 && delta < 256)
+ {
+ yield return state.Apply(new MOVE_STACK(0));
+ }
+
+ // If the first byte offset is not equal to the accumulator value, propose that
+ if (delta > 0)
+ {
+ yield return state.Apply(new MOVE_STACK(delta));
+ }
+
+ // Find the first edge of a solid run....TODO
+ if (firstSolid != null && firstSolid.Count >= 2)
+ {
+ yield return state.Apply(new MOVE_STACK(firstSolid.Last.Offset - state.A.Value));
+ }
+
+ yield break;
+ }
+
+ // If the first byte is 256 bytes or more ahead of the current stack location,
+ // then we need to advance
+ var firstByteDistance = firstByte.Offset - state.S.Value;
+ if (state.S.IsScreenOffset && firstByteDistance >= 256)
+ {
+ // Go to the next byte, or the first solid edge
+ yield return state.Apply(new MOVE_STACK(firstByteDistance));
+
+ yield break;
+ }
+
+ var bytes = open.ToDictionary(x => x.Offset, x => x);
+
// Get the current byte and current word that exist at the current stack location
var topByte = state.TryGetStackByte(bytes);
var topWord = state.TryGetStackWord(bytes);
- var nextWord = state.TryGetStackWord(bytes, -2); // Also get the next value below the current word
- // If there is some data at the top of the stack, see what we can do
- if (topWord.HasValue)
+ // If the top of the stack is a solid work, we can always emit a PEA regardless of 8/16-bit mode
+ if (topWord.HasValue && topWord.Value.Mask == 0x000)
{
- // First, the simple case -- the data has no mask
- if (topWord.Value.Mask == 0x000)
- {
- // If any of the registers has the exact value we need, then it is always fastest to just push the value
- if (state.LongA)
- {
- if (state.A.IsLiteral && state.A.Value == topWord.Value.Data)
- {
- yield return state.Apply(new PHA_16());
- }
- else
- {
- yield return state.Apply(new PEA(topWord.Value.Data));
- yield return state.Apply(new LOAD_16_BIT_IMMEDIATE_AND_PUSH(topWord.Value.Data));
- }
- }
- // Otherwise, the only alternative is a PEA instruction
- else
- {
- yield return state.Apply(new PEA(topWord.Value.Data));
- }
- }
+ yield return state.Apply(new PEA(topWord.Value.Data));
}
- // If there is a valid byte, then we can look for an 8-bit push, or an immediate mode LDA #XX/STA 0,s
- if (topByte.HasValue)
+ // First set of operations for when the accumulator is in 8-bit mode. We are basically limited
+ // to either
+ //
+ // 1. Switching to 16-bit mode
+ // 3. Using a PHA to push an 8-bit solid or masked value on the stack
+ // 4. Using a STA 0,s to store an 8-bit solid or masked value on the stack
+
+ if (!state.LongA)
{
- if (topByte.Value.Mask == 0x00)
+ // The byte to be stored at the top of the stack has some special methods available
+ if (topByte.HasValue)
{
- if (!state.LongA)
+ var datum = topByte.Value;
+
+ // Solid byte
+ if (datum.Mask == 0x00)
{
- if (state.A.IsLiteral && ((state.A.Value & 0xFF) == topByte.Value.Data))
+ if (state.A.IsLiteral && ((state.A.Value & 0xFF) == datum.Data))
{
yield return state.Apply(new PHA_8());
}
else
{
- yield return state.Apply(new LOAD_8_BIT_IMMEDIATE_AND_PUSH(topByte.Value.Data));
+ yield return state.Apply(new LOAD_8_BIT_IMMEDIATE_AND_PUSH(datum.Data));
+ yield return state.Apply(new STACK_REL_8_BIT_IMMEDIATE_STORE(datum.Data, 0));
}
}
- }
- }
- // If the accumulator holds an offset then we could move to any byte position, but it is only beneficial to
- // either
- //
- // 1. Set the stack to the current accumulator value
- // 2. Set the stack to the start of a contiguous segment
- // 3. Set the stack to the end of a contiguous segment
-
- // move to the first or last byte of each span. So, take the first byte and then look for any
- if (state.A.IsScreenOffset && !state.S.IsScreenOffset && state.LongA)
- {
- // If any of the open bytes are within 255 bytes of the accumulator, consider just
- // setting the stack to the accumulator value
- if (open.Any(x => (x.Offset - state.A.Value) >= 0 && (x.Offset - state.A.Value) < 256))
- {
- yield return state.Apply(new MOVE_STACK(0));
- }
-
- for (var i = 0; i < open.Count; i++)
- {
- if (i == 0)
+ // Masked byte
+ else
{
- yield return state.Apply(new MOVE_STACK(open[i].Offset - state.A.Value));
- continue;
- }
-
- if (i == open.Count - 1)
- {
- yield return state.Apply(new MOVE_STACK(open[i].Offset - state.A.Value));
- continue;
- }
-
- if ((open[i].Offset - open[i - 1].Offset) > 1)
- {
- yield return state.Apply(new MOVE_STACK(open[i].Offset - state.A.Value));
+ yield return state.Apply(new STACK_REL_8_BIT_READ_MODIFY_PUSH(datum.Data, datum.Mask));
}
}
- }
- // It is always permissible to move to/from 16 bit mode
- if (state.LongA)
- {
- yield return state.Apply(new SHORT_M());
-
- // Add any possible 16-bit data manipulations
- if (state.S.IsScreenOffset)
- {
- var addr = state.S.Value;
-
- // Look for consecutive bytes. The second byte can come from the DATASET
- var local = open.Where(WithinRangeOf(addr, 256)).ToList();
- var words = local
- .Where(x => SpriteGeneratorState.DATASET_BY_OFFSET.ContainsKey(x.Offset + 1))
- .Select(x => new { Low = x, High = SpriteGeneratorState.DATASET_BY_OFFSET[x.Offset + 1] })
- .ToList();
-
- foreach (var word in words)
- {
- var offset = (byte)(word.Low.Offset - addr);
- var data = (ushort)(word.Low.Data + (word.High.Data << 8));
- var mask = (ushort)(word.Low.Mask + (word.High.Mask << 8));
-
- // Easy case when mask is empty
- if (mask == 0x0000)
- {
- if (data == state.A.Value)
- {
- yield return state.Apply(new STACK_REL_16_BIT_STORE(offset));
- }
- else
- {
- yield return state.Apply(new STACK_REL_16_BIT_IMMEDIATE_STORE(data, offset));
- }
- }
-
- // Otherwise there is really only one choice LDA / AND / ORA / STA sequence
- else
- {
- yield return state.Apply(new STACK_REL_16_BIT_READ_MODIFY_WRITE(data, mask, offset));
- }
- }
- }
- }
- else
- {
- yield return state.Apply(new LONG_M());
-
- // Add any possible 8-bit manipulations
+ // Otherwise, just store the next byte closest to the stack
if (state.S.IsScreenOffset)
{
var addr = state.S.Value;
@@ -268,27 +221,140 @@
}
}
}
+
+ if (state.AllowModeChange)
+ {
+ yield return state.Apply(new LONG_M());
+ }
}
- // If the accumulator and stack are both initialized, only propose moves to locations
- // before and after the current 256 byte stack-relative window
- if (state.S.IsScreenOffset && state.LongA)
+ // Now consider what can be done when the accumulator is 16-bit. All of the stack
+ // manipulation happens here, too.
+ if (state.LongA)
{
- // If the accumulator already has a screen offset value, just calculate it
- if (state.A.IsScreenOffset)
+ // Handle the special case of a value sitting right on top of the stack
+ if (topWord.HasValue)
{
- var addr = state.S.Value;
- foreach (var datum in open.Where(x => (x.Offset - addr) > 255 || (x.Offset - addr) < 0))
+ var datum = topWord.Value;
+
+ // First, the simple case -- the data has no mask
+ if (datum.Mask == 0x000)
{
- yield return state.Apply(new MOVE_STACK(datum.Offset - state.A.Value));
+ if (state.A.IsLiteral && state.A.Value == datum.Data)
+ {
+ yield return state.Apply(new PHA_16());
+ }
+ else
+ {
+ // Only consider this if the value appear in the sprite more than once
+ if (SpriteGeneratorState.DATASET_SOLID_WORDS[topWord.Value.Data] > 1)
+ {
+ yield return state.Apply(new LOAD_16_BIT_IMMEDIATE_AND_PUSH(topWord.Value.Data));
+ }
+ }
}
}
- // Otherwise, put the stack in the accumulator
- else
+
+ // If the stack is set, find the next word to store (just one to reduce branching factor)
+ if (state.S.IsScreenOffset)
{
- yield return state.Apply(new TSC());
+ var addr = state.S.Value;
+
+ var local = open.Where(WithinRangeOf(addr, 256)).ToList();
+ var words = local
+ .Where(x => SpriteGeneratorState.DATASET_BY_OFFSET.ContainsKey(x.Offset + 1))
+ .Select(x => new { Low = x, High = SpriteGeneratorState.DATASET_BY_OFFSET[x.Offset + 1] })
+ .ToList();
+
+ foreach (var word in words)
+ {
+ var offset = (byte)(word.Low.Offset - addr);
+ var data = (ushort)(word.Low.Data + (word.High.Data << 8));
+ var mask = (ushort)(word.Low.Mask + (word.High.Mask << 8));
+ // Easy case when mask is empty
+ if (mask == 0x0000)
+ {
+ if (data == state.A.Value)
+ {
+ yield return state.Apply(new STACK_REL_16_BIT_STORE(offset));
+ }
+ else
+ {
+ yield return state.Apply(new STACK_REL_16_BIT_IMMEDIATE_STORE(data, offset));
+ }
+ }
+
+ // Otherwise there is really only one choice LDA / AND / ORA / STA sequence
+ else
+ {
+ yield return state.Apply(new STACK_REL_16_BIT_READ_MODIFY_WRITE(data, mask, offset));
+ }
+ }
+ }
+
+ if (state.AllowModeChange)
+ {
+ yield return state.Apply(new SHORT_M());
}
}
+
+ Done:
+ var z = 0; z += 1;
+ }
+
+ private static bool IsSolidPair(Tuple pair)
+ {
+ return
+ (pair.Item1.Offset == (pair.Item2.Offset - 1)) &&
+ pair.Item1.Mask == 0x00 &&
+ pair.Item2.Mask == 0x00;
+ }
+
+ private class SolidRun
+ {
+ private readonly SpriteByte first;
+ private readonly SpriteByte last;
+
+ public SolidRun(SpriteByte first, SpriteByte last)
+ {
+ this.first = first;
+ this.last = last;
+ }
+
+ public SpriteByte First { get { return first; } }
+ public SpriteByte Last { get { return last; } }
+ public int Count { get { return last.Offset - first.Offset + 1; } }
+ }
+
+ private SolidRun FirstSolidRun(IEnumerable open)
+ {
+ bool trigger = false;
+ SpriteByte first = default(SpriteByte);
+ SpriteByte last = default(SpriteByte);
+
+ foreach (var item in open)
+ {
+ if (item.Mask == 0x00 && !trigger)
+ {
+ first = item;
+ trigger = true;
+ }
+
+ if (item.Mask != 0x00 && trigger)
+ {
+ return new SolidRun(first, last);
+ }
+
+ last = item;
+ }
+
+ // If we get to the end and are still sold, great
+ if (last.Mask == 0x00 && trigger)
+ {
+ return new SolidRun(first, last);
+ }
+
+ return null;
}
private Func WithinRangeOf(int addr, int range)
diff --git a/SpriteCompiler/SpriteCompiler.csproj b/SpriteCompiler/SpriteCompiler.csproj
index dc62f51..0f4a7ae 100644
--- a/SpriteCompiler/SpriteCompiler.csproj
+++ b/SpriteCompiler/SpriteCompiler.csproj
@@ -68,6 +68,7 @@
+
@@ -87,6 +88,7 @@
+