Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds sprite rendering #22

Merged
merged 1 commit into from
Jul 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Sources/SwiftNES/AddressableDevice.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import Foundation

/// A protocol that describes a device that is addressable on a system bus.
protocol AddressableDevice: AnyObject {
/// The address range of the device.
Expand Down
2 changes: 0 additions & 2 deletions Sources/SwiftNES/AudioReceiver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import Foundation

public protocol AudioReceiver: AnyObject {
// TODO: Define an AudioReceiver.
// TODO: Move protocol into an SDK package.
Expand Down
30 changes: 17 additions & 13 deletions Sources/SwiftNES/Bus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import Foundation

/// Represents a bus that allows the CPU to read and write to various attached devices via memory address.
final class Bus {

Expand All @@ -30,7 +28,7 @@ final class Bus {
/// Initializes a bus with attached devices.
///
/// - Parameters:
/// - addressableDevices: The devices that can be addressed via this bus.
/// - addressableDevices: The ``AddressableDevice``s that can be addressed via the bus.
init(addressableDevices: [any AddressableDevice]) throws {
// TODO: Verify that there are no overlapping devices.

Expand All @@ -49,8 +47,8 @@ final class Bus {
/// Read from or write to an address on the bus.
///
/// - Parameters:
/// - address: The address to read from or write to..
/// - Returns: The value that was read from a device on the bus.
/// - address: The ``Address`` to read from or write to.
/// - Returns: The ``Value`` that was read from a device on the bus.
subscript(address: Address) -> Value {
get { read(from: address) }
set { write(newValue, to: address) }
Expand All @@ -61,8 +59,8 @@ final class Bus {
/// - Note: If a device does not respond to the address, `0` will be returned.
///
/// - Parameters:
/// - address: The address to read from.
/// - Returns: The value that was read from a device on the bus.
/// - address: The ``Address`` to read from.
/// - Returns: The ``Value`` that was read from a device on the bus.
func read(from address: Address) -> Value {
guard let deviceToReadFromIndex = addressableReadDeviceRanges
.firstIndex(where: { $0.contains(address) }) else { return 0 }
Expand All @@ -77,8 +75,8 @@ final class Bus {
/// - Note: If a device does not respond to the address, this method does nothing.
///
/// - Parameters:
/// - value: The value to write to the bus.
/// - address: The address to write to.
/// - value: The ``Value`` to write to the bus.
/// - address: The ``Address`` to write to.
func write(_ value: Value, to address: Address) {
guard let deviceToWriteToIndex = addressableWriteDeviceRanges
.firstIndex(where: { $0.contains(address) }) else { return }
Expand All @@ -91,15 +89,21 @@ final class Bus {

// MARK: - Private

/// The ranges of the addressable devices attached to the bus that can be read from.
/// The ranges of the ``AddressableReadDevice``s attached to the bus.
private let addressableReadDeviceRanges: [AddressRange]

/// The ranges of the addressable devices attached to the bus that can be written to.
/// The ranges of the ``AddressableWriteDevice``s attached to the bus.
private let addressableWriteDeviceRanges: [AddressRange]

/// All of the addressable devices attached to the bus that can be read from.
/// All of the ``AddressableReadDevice``s attached to the bus.
private let addressableReadDevices: [any AddressableReadDevice]

/// All of the addressable devices attached to the bus that can be written to.
/// All of the ``AddressableWriteDevice``s attached to the bus.
private let addressableWriteDevices: [any AddressableWriteDevice]
}

extension Bus: DirectMemoryAccessableReadDevice {
func dmaRead(from address: Address) -> Value {
read(from: address)
}
}
2 changes: 0 additions & 2 deletions Sources/SwiftNES/CartridgeConnector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import Foundation

/// A class that represents a connection to a cartridge.
///
/// Instead of attaching the cartridge directly to the bus, a connector is used to allow for
Expand Down
2 changes: 0 additions & 2 deletions Sources/SwiftNES/ControllerConnector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import Foundation

/// A controller that can be connected to a ``ControllerConnector``.
public protocol Controller: AnyObject {
/// Reads a single bit from the controller.
Expand Down
2 changes: 0 additions & 2 deletions Sources/SwiftNES/Controllers/ControlPad.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import Foundation

/// A class that represents an NES Control Pad (NES-004).
public final class ControlPad: Controller {

Expand Down
119 changes: 106 additions & 13 deletions Sources/SwiftNES/DirectMemoryAccessController.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// MIT License
//
// Copyright (c) 2020 Jerrod Putman
// Copyright (c) 2023 Jerrod Putman
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
Expand All @@ -20,26 +20,119 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import Foundation
/// Errors that are thrown by the ``DirectMemoryAccessController``.
enum DirectMemoryAccessControllerError: Error {
/// The ``DirectMemoryAccessController/readDevice`` was not assigned.
case readDeviceNotAssigned

/// The ``DirectMemoryAccessController/writeDevice`` was not assigned.
case writeDeviceNotAssigned
}

struct ObjectAttributeEntry {
var y: UInt8
var id: UInt8
var attribute: UInt8
var x: UInt8
/// Represents a device that the ``DirectMemoryAccessController`` reads from.
protocol DirectMemoryAccessableReadDevice: AnyObject {
/// Reads a value from the device at the specified address.
///
/// - Parameter address: The ``Address`` of the device to read from.
/// - Returns: The ``Value`` stored at the address on the device.
func dmaRead(from address: Address) -> Value
}

var objectAttributeMemory: [ObjectAttributeEntry] = []
/// Represents a device that the ``DirectMemoryAccessController`` writes to.
protocol DirectMemoryAccessableWriteDevice: AnyObject {
/// Writes a value to the device at the specified address.
///
/// - Parameter value: The ``Value`` to write to the device.
/// - Parameter address: The 8-bit address to write to on the device.
func dmaWrite(_ value: Value, to address: UInt8)
}

final class DirectMemoryAccessController {
/// Represents the direct memory access controller of the NES.
final class DirectMemoryAccessController: AddressableWriteDevice {

init() {
page = 0
address = 0
data = 0
// MARK: - Initializers

/// Initializes the controller.
///
/// - Parameter address: The ``Address`` where the controller can be written to.
init(address: Address) {
self.addressRange = address...address

self.page = 0
self.address = 0
self.data = 0
}


// MARK: - Getting the status of the controller

/// Whether or not a DMA transfer is in progress.
private(set) var isTransferInProgress = false


// MARK: - Transferring data

/// The device to read from during transfers.
weak var readDevice: (any DirectMemoryAccessableReadDevice)?

/// The device to write to during transfers.
weak var writeDevice: (any DirectMemoryAccessableWriteDevice)?

/// Clocks the controller.
///
/// - Parameter cycleCount: The current cycle count of the hardware.
/// - Returns: Whether or not a transfer is in progress.
/// - Throws: A ``DirectMemoryAccessControllerError``.
func clock(cycleCount: UInt) throws -> Bool {
guard isTransferInProgress else { return false }

guard let readDevice else { throw DirectMemoryAccessControllerError.readDeviceNotAssigned }
guard let writeDevice else { throw DirectMemoryAccessControllerError.writeDeviceNotAssigned }

if syncCycle {
if cycleCount % 2 == 1 {
syncCycle = false
}
} else {
if cycleCount % 2 == 0 {
data = readDevice.dmaRead(from: UInt16(lo: address, hi: page))
} else {
writeDevice.dmaWrite(data, to: address)
address &+= 1

if address == 0 {
isTransferInProgress = false
syncCycle = true
}
}
}

return true
}


// MARK: - AddressableWriteDevice

let addressRange: AddressRange

func write(_ data: Value, to address: Address) {
page = data
self.address = 0
isTransferInProgress = true
}


// MARK: - Private

/// The page of memory where the current DMA transfer is occurring.
private var page: UInt8

/// The address of memory, offset from the page, where the current DMA transfer is occurring.
private var address: UInt8

/// The data that is currently being moved from the CPU to the PPU.
private var data: Value

/// Whether or not the transfer should wait for synchronization.
private var syncCycle = true
}
Loading