mirror of
https://github.com/lscharen/iigs-sprite-compiler.git
synced 2024-10-12 03:23:41 +00:00
135 lines
4.4 KiB
C#
135 lines
4.4 KiB
C#
namespace SpriteCompiler.Problem
|
|
{
|
|
using SpriteCompiler.AI;
|
|
using SpriteCompiler.Helpers;
|
|
using System.Linq;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
public sealed class SpriteGeneratorHeuristicFunction : IHeuristicFunction<SpriteGeneratorState, IntegerCost>
|
|
{
|
|
private static int SpanAndGapCost(int stack, int start, int end, int next)
|
|
{
|
|
var len = end - start + 1;
|
|
|
|
// If the span is within 255 bytes of the stack, there is no
|
|
// gap penalty and we base the cost off of sta xx,s instructions
|
|
|
|
var h1 = SpanAndGapCost(start, end, next);
|
|
var h2 = int.MaxValue;
|
|
|
|
if (stack <= end && (end - stack) < 256)
|
|
{
|
|
h2 = 5 * (len / 2) + 4 * (len % 2);
|
|
}
|
|
|
|
return Math.Min(h1, h2);
|
|
}
|
|
|
|
private static int SpanAndGapCost(int start, int end, int next)
|
|
{
|
|
// [start, end] is the span
|
|
// (end, next) is the gap
|
|
//
|
|
// start <= end <= next
|
|
var len = end - start + 1;
|
|
|
|
// No gap, no penalty
|
|
var gapCost = (end == next) ? 0 : 5;
|
|
var spanCost = 4 * (len / 2) + 3 * (len % 2);
|
|
|
|
return gapCost + spanCost;
|
|
}
|
|
|
|
private const int MASKED_DATA = 0;
|
|
private const int SOLID_DATA = 1;
|
|
|
|
private int ClassifyChunk(IEnumerable<SpriteByte> 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
|
|
//
|
|
// An even-length run can be done, at best in 4 cycles/word
|
|
// An odd-length run is even + 3 cycles/byte
|
|
|
|
// Easy case -- no data means no code
|
|
if (state.IsEmpty)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|