<commit_before>#include "CGBASupport.hpp"
#include "CDvdRequest.hpp"
#include "CBasics.hpp"

namespace urde
{
namespace MP1
{

#define GBA_JSTAT_MASK                  0x3a
#define GBA_JSTAT_FLAGS_SHIFT           4
#define GBA_JSTAT_FLAGS_MASK            0x30
#define GBA_JSTAT_PSF1                  0x20
#define GBA_JSTAT_PSF0                  0x10
#define GBA_JSTAT_SEND                  0x08
#define GBA_JSTAT_RECV                  0x02

#define GBA_READY                       0
#define GBA_NOT_READY                   1
#define GBA_BUSY                        2
#define GBA_JOYBOOT_UNKNOWN_STATE       3
#define GBA_JOYBOOT_ERR_INVALID         4

static s32 GBAJoyBootAsync(s32 chan, s32 paletteColor, s32 paletteSpeed,
                           u8* programp, s32 length, u8* status)
{
    return GBA_READY;
}

static s32 GBAGetProcessStatus(s32 chan, u8* percentp)
{
    return GBA_READY;
}

static s32 GBAGetStatus(s32 chan, u8* status)
{
    return GBA_READY;
}

static s32 GBAReset(s32 chan, u8* status)
{
    return GBA_READY;
}

static s32 GBARead(s32 chan, u8* dst, u8* status)
{
    return GBA_READY;
}

static s32 GBAWrite(s32 chan, u8* src, u8* status)
{
    return GBA_READY;
}

CGBASupport* CGBASupport::SharedInstance = nullptr;

CGBASupport::CGBASupport()
: CDvdFile("client_pad.bin")
{
    x28_fileSize = ROUND_UP_32(Length());
    x2c_buffer.reset(new u8[x28_fileSize]);
    x30_dvdReq = AsyncRead(x2c_buffer.get(), x28_fileSize);
    //GBAInit();
    SharedInstance = this;
}

CGBASupport::~CGBASupport()
{
    SharedInstance = nullptr;
}

bool CGBASupport::PollResponse()
{
    u8 status;
    if (GBAReset(x40_siChan, &status) == GBA_NOT_READY)
        if (GBAReset(x40_siChan, &status) == GBA_NOT_READY)
            return false;

    if (GBAGetStatus(x40_siChan, &status) == GBA_NOT_READY)
        return false;
    if (status != (GBA_JSTAT_PSF1 | GBA_JSTAT_SEND))
        return false;

    u8 bytes[4];
    if (GBARead(x40_siChan, bytes, &status) == GBA_NOT_READY)
        return false;
    if (reinterpret_cast<u32&>(bytes) != SBIG('AMTE'))
        return false;

    if (GBAGetStatus(x40_siChan, &status) == GBA_NOT_READY)
        return false;
    if (status != GBA_JSTAT_PSF1)
        return false;

    if (GBAWrite(x40_siChan, (unsigned char*)"AMTE", &status) == GBA_NOT_READY)
        return false;

    if (GBAGetStatus(x40_siChan, &status) == GBA_NOT_READY)
        return false;
    if ((status & GBA_JSTAT_FLAGS_MASK) != GBA_JSTAT_FLAGS_MASK)
        return false;

    u64 profStart = CBasics::GetGCTicks();
    const u64 timeToSpin = 486000000 / 8000;
    for (;;)
    {
        u64 curTime = CBasics::GetGCTicks();
        if (curTime - profStart > timeToSpin)
            return true;

        if (GBAGetStatus(x40_siChan, &status) == GBA_NOT_READY)
            continue;
        if (!(status & GBA_JSTAT_SEND))
            continue;

        if (GBAGetStatus(x40_siChan, &status) == GBA_NOT_READY)
            continue;
        if (status != (GBA_JSTAT_FLAGS_MASK | GBA_JSTAT_SEND))
            continue;
        break;
    }

    if (GBARead(x40_siChan, bytes, &status) != GBA_READY)
        return false;

    /* CRC Here */

    x44_fusionLinked = (bytes[2] & 0x2) != 0;
    if (x44_fusionLinked && (bytes[2] & 0x1) != 0)
        x45_fusionBeat = true;

    return true;
}

void CGBASupport::Update(float dt)
{
    switch (x34_phase)
    {
    case EPhase::LoadClientPad:
        IsReady();
        break;

    case EPhase::StartProbeTimeout:
        x38_timeout = 4.f;
        x34_phase = EPhase::PollProbe;

    case EPhase::PollProbe:
        /* SIProbe poll normally occurs here with 4 second timeout */
        x34_phase = EPhase::StartJoyBusBoot;

    case EPhase::StartJoyBusBoot:
        x34_phase = EPhase::PollJoyBusBoot;
        GBAJoyBootAsync(x40_siChan, x40_siChan * 2, 2, x2c_buffer.get(), x28_fileSize, &x3c_status);
        break;

    case EPhase::PollJoyBusBoot:
        if (GBAGetProcessStatus(x40_siChan, &x3c_status) == GBA_BUSY)
            break;
        if (GBAGetStatus(x40_siChan, &x3c_status) == GBA_NOT_READY)
        {
            x34_phase = EPhase::Failed;
            break;
        }
        x38_timeout = 4.f;
        x34_phase = EPhase::DataTransfer;
        break;

    case EPhase::DataTransfer:
        if (PollResponse())
        {
            x34_phase = EPhase::Complete;
            break;
        }
        x38_timeout = std::max(0.f, x38_timeout - dt);
        if (x38_timeout == 0.f)
            x34_phase = EPhase::Failed;
        break;

    default: break;
    }
}

bool CGBASupport::IsReady()
{
    if (x34_phase != EPhase::LoadClientPad)
        return true;

    if (x30_dvdReq->IsComplete())
    {
        x30_dvdReq.reset();
        x34_phase = EPhase::Standby;
        /* Conveniently already little-endian */
        reinterpret_cast<u32&>(x2c_buffer[0xc8]) = u32(CBasics::GetGCTicks());
        x2c_buffer[0xaf] = 'E';
        x2c_buffer[0xbd] = 0xc9;
        return true;
    }
    return false;
}

void CGBASupport::InitializeSupport()
{
    x34_phase = EPhase::Standby;
    x38_timeout = 0.f;
    x3c_status = false;
    x40_siChan = -1;
    x44_fusionLinked = false;
    x45_fusionBeat = false;
}

void CGBASupport::StartLink()
{
    x34_phase = EPhase::StartProbeTimeout;
    x40_siChan = -1;
}

}
}
<commit_msg>Integrate JoyBus checksum<commit_after>#include "CGBASupport.hpp"
#include "CDvdRequest.hpp"
#include "CBasics.hpp"

namespace urde
{
namespace MP1
{

#define GBA_JSTAT_MASK                  0x3a
#define GBA_JSTAT_FLAGS_SHIFT           4
#define GBA_JSTAT_FLAGS_MASK            0x30
#define GBA_JSTAT_PSF1                  0x20
#define GBA_JSTAT_PSF0                  0x10
#define GBA_JSTAT_SEND                  0x08
#define GBA_JSTAT_RECV                  0x02

#define GBA_READY                       0
#define GBA_NOT_READY                   1
#define GBA_BUSY                        2
#define GBA_JOYBOOT_UNKNOWN_STATE       3
#define GBA_JOYBOOT_ERR_INVALID         4

static void GBAInit()
{

}

static s32 GBAJoyBootAsync(s32 chan, s32 paletteColor, s32 paletteSpeed,
                           u8* programp, s32 length, u8* status)
{
    return GBA_READY;
}

static s32 GBAGetProcessStatus(s32 chan, u8* percentp)
{
    return GBA_READY;
}

static s32 GBAGetStatus(s32 chan, u8* status)
{
    return GBA_READY;
}

static s32 GBAReset(s32 chan, u8* status)
{
    return GBA_READY;
}

static s32 GBARead(s32 chan, u8* dst, u8* status)
{
    return GBA_READY;
}

static s32 GBAWrite(s32 chan, u8* src, u8* status)
{
    return GBA_READY;
}

static u32 calculateJBusChecksum(const u8* data, size_t len)
{
    const u8* ptr = data;
    u32 sum = -1;
    for (int i = len ; i > 0; --i)
    {
        u8 ch = *ptr;
        ptr++;
        sum ^= ch;
        for (int j = 0; j < 8; ++j)
        {
            if ((sum & 1))
            {
                sum >>= 1;
                sum ^= 0xb010;
            }
            else
                sum >>= 1;
        }
    }

    return sum;
}

CGBASupport* CGBASupport::SharedInstance = nullptr;

CGBASupport::CGBASupport()
: CDvdFile("client_pad.bin")
{
    x28_fileSize = ROUND_UP_32(Length());
    x2c_buffer.reset(new u8[x28_fileSize]);
    x30_dvdReq = AsyncRead(x2c_buffer.get(), x28_fileSize);
    GBAInit();
    SharedInstance = this;
}

CGBASupport::~CGBASupport()
{
    SharedInstance = nullptr;
}

bool CGBASupport::PollResponse()
{
    u8 status;
    if (GBAReset(x40_siChan, &status) == GBA_NOT_READY)
        if (GBAReset(x40_siChan, &status) == GBA_NOT_READY)
            return false;

    if (GBAGetStatus(x40_siChan, &status) == GBA_NOT_READY)
        return false;
    if (status != (GBA_JSTAT_PSF1 | GBA_JSTAT_SEND))
        return false;

    u8 bytes[4];
    if (GBARead(x40_siChan, bytes, &status) == GBA_NOT_READY)
        return false;
    if (reinterpret_cast<u32&>(bytes) != SBIG('AMTE'))
        return false;

    if (GBAGetStatus(x40_siChan, &status) == GBA_NOT_READY)
        return false;
    if (status != GBA_JSTAT_PSF1)
        return false;

    if (GBAWrite(x40_siChan, (unsigned char*)"AMTE", &status) == GBA_NOT_READY)
        return false;

    if (GBAGetStatus(x40_siChan, &status) == GBA_NOT_READY)
        return false;
    if ((status & GBA_JSTAT_FLAGS_MASK) != GBA_JSTAT_FLAGS_MASK)
        return false;

    u64 profStart = CBasics::GetGCTicks();
    const u64 timeToSpin = 486000000 / 8000;
    for (;;)
    {
        u64 curTime = CBasics::GetGCTicks();
        if (curTime - profStart > timeToSpin)
            return true;

        if (GBAGetStatus(x40_siChan, &status) == GBA_NOT_READY)
            continue;
        if (!(status & GBA_JSTAT_SEND))
            continue;

        if (GBAGetStatus(x40_siChan, &status) == GBA_NOT_READY)
            continue;
        if (status != (GBA_JSTAT_FLAGS_MASK | GBA_JSTAT_SEND))
            continue;
        break;
    }

    if (GBARead(x40_siChan, bytes, &status) != GBA_READY)
        return false;

    u32 swapped = hecl::SBig(reinterpret_cast<u32&>(bytes));
    if (bytes[0] != calculateJBusChecksum(reinterpret_cast<u8*>(&swapped), 3))
        return false;

    x44_fusionLinked = (bytes[2] & 0x2) != 0;
    if (x44_fusionLinked && (bytes[2] & 0x1) != 0)
        x45_fusionBeat = true;

    return true;
}

void CGBASupport::Update(float dt)
{
    switch (x34_phase)
    {
    case EPhase::LoadClientPad:
        IsReady();
        break;

    case EPhase::StartProbeTimeout:
        x38_timeout = 4.f;
        x34_phase = EPhase::PollProbe;

    case EPhase::PollProbe:
        /* SIProbe poll normally occurs here with 4 second timeout */
        x34_phase = EPhase::StartJoyBusBoot;

    case EPhase::StartJoyBusBoot:
        x34_phase = EPhase::PollJoyBusBoot;
        GBAJoyBootAsync(x40_siChan, x40_siChan * 2, 2, x2c_buffer.get(), x28_fileSize, &x3c_status);
        break;

    case EPhase::PollJoyBusBoot:
        if (GBAGetProcessStatus(x40_siChan, &x3c_status) == GBA_BUSY)
            break;
        if (GBAGetStatus(x40_siChan, &x3c_status) == GBA_NOT_READY)
        {
            x34_phase = EPhase::Failed;
            break;
        }
        x38_timeout = 4.f;
        x34_phase = EPhase::DataTransfer;
        break;

    case EPhase::DataTransfer:
        if (PollResponse())
        {
            x34_phase = EPhase::Complete;
            break;
        }
        x38_timeout = std::max(0.f, x38_timeout - dt);
        if (x38_timeout == 0.f)
            x34_phase = EPhase::Failed;
        break;

    default: break;
    }
}

bool CGBASupport::IsReady()
{
    if (x34_phase != EPhase::LoadClientPad)
        return true;

    if (x30_dvdReq->IsComplete())
    {
        x30_dvdReq.reset();
        x34_phase = EPhase::Standby;
        /* Conveniently already little-endian */
        reinterpret_cast<u32&>(x2c_buffer[0xc8]) = u32(CBasics::GetGCTicks());
        x2c_buffer[0xaf] = 'E';
        x2c_buffer[0xbd] = 0xc9;
        return true;
    }
    return false;
}

void CGBASupport::InitializeSupport()
{
    x34_phase = EPhase::Standby;
    x38_timeout = 0.f;
    x3c_status = false;
    x40_siChan = -1;
    x44_fusionLinked = false;
    x45_fusionBeat = false;
}

void CGBASupport::StartLink()
{
    x34_phase = EPhase::StartProbeTimeout;
    x40_siChan = -1;
}

}
}
<|endoftext|>


<commit_before>//===----------------------------------------------------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is dual licensed under the MIT and the University of Illinois Open
// Source Licenses. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// XFAIL: apple-darwin

// test quick_exit and at_quick_exit

#include <cstdlib>

void f() {}

int main()
{
#ifdef _LIBCPP_HAS_QUICK_EXIT
    std::at_quick_exit(f);
    std::quick_exit(0);
#endif
}
<commit_msg>Remove xfail tag for darwin from quick_exit test<commit_after>//===----------------------------------------------------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is dual licensed under the MIT and the University of Illinois Open
// Source Licenses. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//

// test quick_exit and at_quick_exit

#include <cstdlib>

void f() {}

int main()
{
#ifdef _LIBCPP_HAS_QUICK_EXIT
    std::at_quick_exit(f);
    std::quick_exit(0);
#endif
}
<|endoftext|>


<commit_before>/**********************************************************************
 Copyright (c) 2016 Advanced Micro Devices, Inc. All rights reserved.

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 ********************************************************************/
#include "camera.h"

#include <cmath>
#include <cassert>

#include "math/quaternion.h"
#include "math/matrix.h"
#include "math/mathutils.h"

namespace Baikal
{
    using namespace RadeonRays;

    Camera::Camera(float3 const& eye, float3 const& at, float3 const& up)
    : m_zcap(0.f, 1000.f)
    , m_dim(0.1f, 0.1f)
    {
        LookAt(eye, at, up);
    }

    PerspectiveCamera::PerspectiveCamera(float3 const& eye, float3 const& at, float3 const& up)
    : Camera(eye, at, up)
    , m_aperture(0.f)
    , m_focus_distance(0.f)
    , m_focal_length(0.f)
    {
    }

    void Camera::LookAt(RadeonRays::float3 const& eye,
                        RadeonRays::float3 const& at,
                        RadeonRays::float3 const& up)
    {
        m_p = eye;
        m_forward = normalize(at - eye);
        m_right = normalize(cross(m_forward, up));
        m_up = cross(m_right, m_forward);
        SetDirty(true);
    }

    // Rotate camera around world Z axis, use for FPS camera
    void Camera::Rotate(float angle)
    {
        Rotate(float3(0.f, 1.f, 0.f), angle);
        SetDirty(true);
    }

    void Camera::Rotate(float3 v, float angle)
    {
        /// matrix should have basis vectors in rows
        /// to be used for quaternion construction
        /// would be good to add several options
        /// to quaternion class
        matrix cam_matrix = matrix(
                                   m_up.x, m_up.y, m_up.z, 0.f,
                                   m_right.x, m_right.y, m_right.z, 0.f,
                                   m_forward.x, m_forward.y, m_forward.z, 0.f,
                                   0.f, 0.f, 0.f, 1.f);

        // Create camera orientation quaternion
        quaternion q = normalize(quaternion(cam_matrix));

        // Rotate camera frame around v
        q = q * rotation_quaternion(v, -angle);

        // Uncomress back to lookat
        q.to_matrix(cam_matrix);

        m_up = normalize(float3(cam_matrix.m00, cam_matrix.m01, cam_matrix.m02));
        m_right = normalize(float3(cam_matrix.m10, cam_matrix.m11, cam_matrix.m12));
        m_forward = normalize(float3(cam_matrix.m20, cam_matrix.m21, cam_matrix.m22));
        SetDirty(true);
    }

    // Tilt camera
    void Camera::Tilt(float angle)
    {
        Rotate(m_right, angle);
        SetDirty(true);
    }

    // Move along camera Z direction
    void Camera::MoveForward(float distance)
    {
        m_p += distance * m_forward;
        SetDirty(true);
    }

    // Move along camera X direction
    void Camera::MoveRight(float distance)
    {
        m_p += distance * m_right;
        SetDirty(true);
    }

    // Move along camera Y direction
    void Camera::MoveUp(float distance)
    {
        m_p += distance * m_up;
        SetDirty(true);
    }

    RadeonRays::float3 Camera::GetForwardVector() const
    {
        return m_forward;
    }

    RadeonRays::float3 Camera::GetUpVector() const
    {
        return m_up;
    }

    RadeonRays::float3 Camera::GetRightVector() const
    {
        return m_right;
    }

    RadeonRays::float3 Camera::GetPosition() const
    {
        return m_p;
    }

    float Camera::GetAspectRatio() const
    {
        return m_dim.x / m_dim.y;
    }

    namespace {
        struct PerspectiveCameraConcrete : public PerspectiveCamera {
            PerspectiveCameraConcrete(float3 const& eye, float3 const& at, float3 const& up) :
            PerspectiveCamera(eye, at, up) {}
        };

        struct OrthographicCameraConcrete : public OrthographicCamera {
            OrthographicCameraConcrete(float3 const& eye, float3 const& at, float3 const& up) :
            OrthographicCamera(eye, at, up) {}
        };
    }

    PerspectiveCamera::Ptr PerspectiveCamera::Create(RadeonRays::float3 const& eye,
               RadeonRays::float3 const& at,
               RadeonRays::float3 const& up) {
        return std::make_shared<PerspectiveCameraConcrete>(eye, at, up);

    }

    OrthographicCamera::Ptr OrthographicCamera::Create(RadeonRays::float3 const& eye,
                                                     RadeonRays::float3 const& at,
                                                     RadeonRays::float3 const& up) {
        return std::make_shared<OrthographicCameraConcrete>(eye, at, up);
    }
}
<commit_msg>Rollback changes to default params<commit_after>/**********************************************************************
 Copyright (c) 2016 Advanced Micro Devices, Inc. All rights reserved.

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 ********************************************************************/
#include "camera.h"

#include <cmath>
#include <cassert>

#include "math/quaternion.h"
#include "math/matrix.h"
#include "math/mathutils.h"

namespace Baikal
{
    using namespace RadeonRays;

    Camera::Camera(float3 const& eye, float3 const& at, float3 const& up)
    {
        LookAt(eye, at, up);
    }

    PerspectiveCamera::PerspectiveCamera(float3 const& eye, float3 const& at, float3 const& up)
    : Camera(eye, at, up)
    , m_aperture(0.f)
    , m_focus_distance(0.f)
    , m_focal_length(0.f)
    {
    }

    void Camera::LookAt(RadeonRays::float3 const& eye,
                        RadeonRays::float3 const& at,
                        RadeonRays::float3 const& up)
    {
        m_p = eye;
        m_forward = normalize(at - eye);
        m_right = normalize(cross(m_forward, up));
        m_up = cross(m_right, m_forward);
        SetDirty(true);
    }

    // Rotate camera around world Z axis, use for FPS camera
    void Camera::Rotate(float angle)
    {
        Rotate(float3(0.f, 1.f, 0.f), angle);
        SetDirty(true);
    }

    void Camera::Rotate(float3 v, float angle)
    {
        /// matrix should have basis vectors in rows
        /// to be used for quaternion construction
        /// would be good to add several options
        /// to quaternion class
        matrix cam_matrix = matrix(
                                   m_up.x, m_up.y, m_up.z, 0.f,
                                   m_right.x, m_right.y, m_right.z, 0.f,
                                   m_forward.x, m_forward.y, m_forward.z, 0.f,
                                   0.f, 0.f, 0.f, 1.f);

        // Create camera orientation quaternion
        quaternion q = normalize(quaternion(cam_matrix));

        // Rotate camera frame around v
        q = q * rotation_quaternion(v, -angle);

        // Uncomress back to lookat
        q.to_matrix(cam_matrix);

        m_up = normalize(float3(cam_matrix.m00, cam_matrix.m01, cam_matrix.m02));
        m_right = normalize(float3(cam_matrix.m10, cam_matrix.m11, cam_matrix.m12));
        m_forward = normalize(float3(cam_matrix.m20, cam_matrix.m21, cam_matrix.m22));
        SetDirty(true);
    }

    // Tilt camera
    void Camera::Tilt(float angle)
    {
        Rotate(m_right, angle);
        SetDirty(true);
    }

    // Move along camera Z direction
    void Camera::MoveForward(float distance)
    {
        m_p += distance * m_forward;
        SetDirty(true);
    }

    // Move along camera X direction
    void Camera::MoveRight(float distance)
    {
        m_p += distance * m_right;
        SetDirty(true);
    }

    // Move along camera Y direction
    void Camera::MoveUp(float distance)
    {
        m_p += distance * m_up;
        SetDirty(true);
    }

    RadeonRays::float3 Camera::GetForwardVector() const
    {
        return m_forward;
    }

    RadeonRays::float3 Camera::GetUpVector() const
    {
        return m_up;
    }

    RadeonRays::float3 Camera::GetRightVector() const
    {
        return m_right;
    }

    RadeonRays::float3 Camera::GetPosition() const
    {
        return m_p;
    }

    float Camera::GetAspectRatio() const
    {
        return m_dim.x / m_dim.y;
    }

    namespace {
        struct PerspectiveCameraConcrete : public PerspectiveCamera {
            PerspectiveCameraConcrete(float3 const& eye, float3 const& at, float3 const& up) :
            PerspectiveCamera(eye, at, up) {}
        };

        struct OrthographicCameraConcrete : public OrthographicCamera {
            OrthographicCameraConcrete(float3 const& eye, float3 const& at, float3 const& up) :
            OrthographicCamera(eye, at, up) {}
        };
    }

    PerspectiveCamera::Ptr PerspectiveCamera::Create(RadeonRays::float3 const& eye,
               RadeonRays::float3 const& at,
               RadeonRays::float3 const& up) {
        return std::make_shared<PerspectiveCameraConcrete>(eye, at, up);

    }

    OrthographicCamera::Ptr OrthographicCamera::Create(RadeonRays::float3 const& eye,
                                                     RadeonRays::float3 const& at,
                                                     RadeonRays::float3 const& up) {
        return std::make_shared<OrthographicCameraConcrete>(eye, at, up);
    }
}
<|endoftext|>


<commit_before>/*$Id$
 *
 * This source file is a part of the Berlin Project.
 * Copyright (C) 1999 Stefan Seefeld <stefan@berlin-consortium.org>
 * http://www.berlin-consortium.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 675 Mass Ave, Cambridge,
 * MA 02139, USA.
 */
#ifndef _Box_hh
#define _Box_hh

#include <Berlin/PolyGraphic.hh>
#include <Layout/LayoutManager.hh>

class Box : public PolyGraphic
{
public:
  Box(LayoutManager *);
  virtual ~Box();

  virtual void request(Warsaw::Graphic::Requisition &);
  virtual void extension(const Warsaw::Allocation::Info &, Warsaw::Region_ptr);

  virtual void traverse(Warsaw::Traversal_ptr);
  virtual void need_resize();
  virtual void need_resize(Warsaw::Tag);
  virtual void allocate(Warsaw::Tag, const Warsaw::Allocation::Info &);

protected:
  LayoutManager::Allocations  children_allocations(Warsaw::Region_ptr);
  void traverse_with_allocation(Warsaw::Traversal_ptr, Warsaw::Region_ptr);
  void traverse_without_allocation(Warsaw::Traversal_ptr);
private:
  LayoutManager *layout;
  bool requested;
  Warsaw::Graphic::Requisition requisition;
};

class BoxAlignElements : public Box
{
public:
  BoxAlignElements(LayoutManager *, Warsaw::Axis, Warsaw::Alignment);
  virtual ~BoxAlignElements();

  virtual void append_graphic(Warsaw::Graphic_ptr);
  virtual void prepend_graphic(Warsaw::Graphic_ptr);
private:
  Warsaw::Axis axis;
  Warsaw::Alignment alignment;
};

class HBox : public Box
{
public:
  HBox() : Box(new LayoutSuperpose(new LayoutTile(Warsaw::xaxis), new LayoutAlign(Warsaw::yaxis), new LayoutAlign(Warsaw::zaxis))) {}
};

class VBox : public Box
{
public:
  VBox() : Box(new LayoutSuperpose(new LayoutTile(Warsaw::yaxis), new LayoutAlign(Warsaw::xaxis), new LayoutAlign(Warsaw::zaxis))) {}
};

class HBoxFirstAligned : public Box
{
public:
  HBoxFirstAligned() : Box(new LayoutSuperpose(new LayoutTileFirstAligned(Warsaw::xaxis), new LayoutAlign(Warsaw::yaxis))) {}
};

class VBoxFirstAligned : public Box
{
public:
  VBoxFirstAligned() : Box(new LayoutSuperpose(new LayoutTileReversedFirstAligned(Warsaw::yaxis), new LayoutAlign(Warsaw::xaxis))) {}
};

class HBoxAlignElements : public BoxAlignElements
{
public:
  HBoxAlignElements(double align)
    : BoxAlignElements(new LayoutSuperpose(new LayoutTile(Warsaw::xaxis), new LayoutAlign(Warsaw::yaxis)), Warsaw::yaxis, align) {}
};

class VBoxAlignElements : public BoxAlignElements
{
public:
  VBoxAlignElements(double align)
    : BoxAlignElements(new LayoutSuperpose(new LayoutTileReversed(Warsaw::yaxis), new LayoutAlign(Warsaw::xaxis)), Warsaw::xaxis, align) {}
};

class Overlay : public Box
{
public:
  Overlay() : Box(new LayoutSuperpose(new LayoutAlign(Warsaw::xaxis, true), new LayoutAlign(Warsaw::yaxis, true))) {}
};

#endif
<commit_msg>(Overlay::Overlay): Removed the second parameter from the `LayoutAlign' ctor invocation.<commit_after>/*$Id$
 *
 * This source file is a part of the Berlin Project.
 * Copyright (C) 1999 Stefan Seefeld <stefan@berlin-consortium.org>
 * http://www.berlin-consortium.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 675 Mass Ave, Cambridge,
 * MA 02139, USA.
 */
#ifndef _Box_hh
#define _Box_hh

#include <Berlin/PolyGraphic.hh>
#include <Layout/LayoutManager.hh>

class Box : public PolyGraphic
{
public:
  Box(LayoutManager *);
  virtual ~Box();

  virtual void request(Warsaw::Graphic::Requisition &);
  virtual void extension(const Warsaw::Allocation::Info &, Warsaw::Region_ptr);

  virtual void traverse(Warsaw::Traversal_ptr);
  virtual void need_resize();
  virtual void need_resize(Warsaw::Tag);
  virtual void allocate(Warsaw::Tag, const Warsaw::Allocation::Info &);

protected:
  LayoutManager::Allocations  children_allocations(Warsaw::Region_ptr);
  void traverse_with_allocation(Warsaw::Traversal_ptr, Warsaw::Region_ptr);
  void traverse_without_allocation(Warsaw::Traversal_ptr);
private:
  LayoutManager *layout;
  bool requested;
  Warsaw::Graphic::Requisition requisition;
};

class BoxAlignElements : public Box
{
public:
  BoxAlignElements(LayoutManager *, Warsaw::Axis, Warsaw::Alignment);
  virtual ~BoxAlignElements();

  virtual void append_graphic(Warsaw::Graphic_ptr);
  virtual void prepend_graphic(Warsaw::Graphic_ptr);
private:
  Warsaw::Axis axis;
  Warsaw::Alignment alignment;
};

class HBox : public Box
{
public:
  HBox() : Box(new LayoutSuperpose(new LayoutTile(Warsaw::xaxis), new LayoutAlign(Warsaw::yaxis), new LayoutAlign(Warsaw::zaxis))) {}
};

class VBox : public Box
{
public:
  VBox() : Box(new LayoutSuperpose(new LayoutTile(Warsaw::yaxis), new LayoutAlign(Warsaw::xaxis), new LayoutAlign(Warsaw::zaxis))) {}
};

class HBoxFirstAligned : public Box
{
public:
  HBoxFirstAligned() : Box(new LayoutSuperpose(new LayoutTileFirstAligned(Warsaw::xaxis), new LayoutAlign(Warsaw::yaxis))) {}
};

class VBoxFirstAligned : public Box
{
public:
  VBoxFirstAligned() : Box(new LayoutSuperpose(new LayoutTileReversedFirstAligned(Warsaw::yaxis), new LayoutAlign(Warsaw::xaxis))) {}
};

class HBoxAlignElements : public BoxAlignElements
{
public:
  HBoxAlignElements(double align)
    : BoxAlignElements(new LayoutSuperpose(new LayoutTile(Warsaw::xaxis), new LayoutAlign(Warsaw::yaxis)), Warsaw::yaxis, align) {}
};

class VBoxAlignElements : public BoxAlignElements
{
public:
  VBoxAlignElements(double align)
    : BoxAlignElements(new LayoutSuperpose(new LayoutTileReversed(Warsaw::yaxis), new LayoutAlign(Warsaw::xaxis)), Warsaw::xaxis, align) {}
};

class Overlay : public Box
{
public:
  Overlay() : Box(new LayoutSuperpose(new LayoutAlign(Warsaw::xaxis), new LayoutAlign(Warsaw::yaxis))) {}
};

#endif
<|endoftext|>


<commit_before>// Time:  O(max(m, n))
// Space: O(1)

class Solution { // TLE
public:
    /**
     * @param A: An integer matrix
     * @return: The index of the peak
     */
    vector<int> findPeakII(vector<vector<int> > A) {
        int upper = 0, down = A.size() - 1;
        int left = 0, right = A[0].size() - 1;

        while (upper < down && left < right) {
            int height = upper - down + 1;
            int width = right - left + 1;

            // T(m, n) = T(m, n / 2) + O(m) = T(m / 2, n / 2) + O(m) + O(n / 2) = ... = O(2m) + O(2n) = O(max(m, n))
            if (width > height) { // Vertical split.
                int mid_j = left + (right - left) / 2;
                int left_max = 0, central_max = 0, right_max = 0;
                int max_i = -1, max_j = -1;
                for (int i = upper + 1; i < down; ++i) {
                    if (A[i][mid_j] > central_max) {
                        max_i = i, max_j = mid_j;
                        central_max = A[i][mid_j];
                    }
                    left_max = max(left_max, A[i][mid_j - 1]);
                    right_max = max(right_max, A[i][mid_j +  1]);
                }
                if (left_max > central_max && left_max > right_max) { // Find left.
                    right = mid_j;
                } else if (right_max > central_max && right_max > left_max) { // Find right.
                    left = mid_j;
                } else { // Find one peak.
                    return {max_i, max_j};
                }
            } else { // Horizontal split.
                int mid_i = upper + (down - upper) / 2;
                int upper_max = 0, central_max = 0, down_max = 0;
                int max_i = -1, max_j = -1;
                for (int j = left + 1; j < right; ++j) {
                    if (A[mid_i][j] > central_max) {
                        max_i = mid_i, max_j = j;
                        central_max = A[mid_i][j];
                    }
                    upper_max = max(upper_max, A[mid_i - 1][j]);
                    down_max = max(down_max, A[mid_i + 1][j]);
                }
                if (upper_max > central_max && upper_max > down_max) { // Find upper.
                    down = mid_i;
                } else if (down_max > central_max && down_max > upper_max) { // Find down.
                    upper = mid_i;
                } else { // Find one peak.
                    return {max_i, max_j};
                }
            }

        }
        return {-1, -1}; // Not found.
    }
};
<commit_msg>Update find-peak-element-ii.cpp<commit_after>// Time:  O(max(m, n))
// Space: O(1)

class Solution { // TLE, test case #15 it never returns even if the function directly returns without computing.
public:
    /**
     * @param A: An integer matrix
     * @return: The index of the peak
     */
    vector<int> findPeakII(vector<vector<int> > A) {
        int upper = 0, down = A.size() - 1;
        int left = 0, right = A[0].size() - 1;

        while (upper < down && left < right) {
            int height = upper - down + 1;
            int width = right - left + 1;

            // T(m, n) = T(m, n / 2) + O(m) = T(m / 2, n / 2) + O(m) + O(n / 2) = ... = O(2m) + O(2n) = O(max(m, n))
            if (width > height) { // Vertical split.
                int mid_j = left + (right - left) / 2;
                int left_max = 0, central_max = 0, right_max = 0;
                int max_i = -1, max_j = -1;
                for (int i = upper + 1; i < down; ++i) {
                    if (A[i][mid_j] > central_max) {
                        max_i = i, max_j = mid_j;
                        central_max = A[i][mid_j];
                    }
                    left_max = max(left_max, A[i][mid_j - 1]);
                    right_max = max(right_max, A[i][mid_j +  1]);
                }
                if (left_max > central_max && left_max > right_max) { // Find left.
                    right = mid_j;
                } else if (right_max > central_max && right_max > left_max) { // Find right.
                    left = mid_j;
                } else { // Find one peak.
                    return {max_i, max_j};
                }
            } else { // Horizontal split.
                int mid_i = upper + (down - upper) / 2;
                int upper_max = 0, central_max = 0, down_max = 0;
                int max_i = -1, max_j = -1;
                for (int j = left + 1; j < right; ++j) {
                    if (A[mid_i][j] > central_max) {
                        max_i = mid_i, max_j = j;
                        central_max = A[mid_i][j];
                    }
                    upper_max = max(upper_max, A[mid_i - 1][j]);
                    down_max = max(down_max, A[mid_i + 1][j]);
                }
                if (upper_max > central_max && upper_max > down_max) { // Find upper.
                    down = mid_i;
                } else if (down_max > central_max && down_max > upper_max) { // Find down.
                    upper = mid_i;
                } else { // Find one peak.
                    return {max_i, max_j};
                }
            }

        }
        return {-1, -1}; // Not found.
    }
};
<|endoftext|>


<commit_before>// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.

#include "UnrealEnginePythonPrivatePCH.h"

void unreal_engine_init_py_module();

#if PY_MAJOR_VERSION < 3
char *PyUnicode_AsUTF8(PyObject *py_str) {
	if (PyUnicode_Check(py_str)) {
		PyObject *unicode = PyUnicode_AsUTF8String(py_str);
		if (unicode) {
			return PyString_AsString(unicode);
		}
		// just a hack to avoid crashes
		return (char *)"<invalid_string>";
	}
	return PyString_AsString(py_str);
}
#endif

#define LOCTEXT_NAMESPACE "FUnrealEnginePythonModule"

void FUnrealEnginePythonModule::PythonGILRelease() {
#if UEPY_THREADING
	ue_python_gil = PyEval_SaveThread();
#endif
}

void FUnrealEnginePythonModule::PythonGILAcquire() {
#if UEPY_THREADING
	PyEval_RestoreThread((PyThreadState *)ue_python_gil);
#endif
}

void FUnrealEnginePythonModule::StartupModule()
{
	// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module

	Py_Initialize();
#if PY_MAJOR_VERSION >= 3
	wchar_t *argv[] = { UTF8_TO_TCHAR("UnrealEngine"), NULL };
#else
	char *argv[] = { (char *)"UnrealEngine", NULL };
#endif
	PySys_SetArgv(1, argv);

	PyEval_InitThreads();

	unreal_engine_init_py_module();

	PyObject *py_sys = PyImport_ImportModule("sys");
	PyObject *py_sys_dict = PyModule_GetDict(py_sys);

	PyObject *py_path = PyDict_GetItemString(py_sys_dict, "path");

	char *zip_path = TCHAR_TO_UTF8(*FPaths::Combine(*FPaths::GameContentDir(), UTF8_TO_TCHAR("python35.zip")));
	PyObject *py_zip_path = PyUnicode_FromString(zip_path);
	PyList_Insert(py_path, 0, py_zip_path);

	char *scripts_path = TCHAR_TO_UTF8(*FPaths::Combine(*FPaths::GameContentDir(), UTF8_TO_TCHAR("Scripts")));
	PyObject *py_scripts_path = PyUnicode_FromString(scripts_path);
	PyList_Insert(py_path, 0, py_scripts_path);

	UE_LOG(LogPython, Log, TEXT("Python VM initialized: %s"), UTF8_TO_TCHAR(Py_GetVersion()));
	UE_LOG(LogPython, Log, TEXT("Python Scripts search path: %s"), UTF8_TO_TCHAR(scripts_path));

	PyObject *main_module = PyImport_AddModule("__main__");
	main_dict = PyModule_GetDict(main_module);
	local_dict = main_dict;// PyDict_New();

	if (PyImport_ImportModule("ue_site")) {
		UE_LOG(LogPython, Log, TEXT("ue_site Python module successfully imported"));
	}
	else {
		// TODO gracefully manage the error
		unreal_engine_py_log_error();
	}

#if WITH_EDITOR
	// register commands (after importing ue_site)
	FPythonSlateCommands::Register();
	// apply extenders
	FPythonSlateCommands::ApplyPythonExtenders();
#endif

	// release the GIL
	PythonGILRelease();

}

void FUnrealEnginePythonModule::ShutdownModule()
{
	// This function may be called during shutdown to clean up your module.  For modules that support dynamic reloading,
	// we call this function before unloading the module.

	UE_LOG(LogPython, Log, TEXT("Goodbye Python"));
	PythonGILAcquire();
	Py_Finalize();
}

void FUnrealEnginePythonModule::RunString(char *str) {
	FScopePythonGIL gil;
	PyObject *eval_ret = PyRun_String(str, Py_file_input, (PyObject *)main_dict, (PyObject *)local_dict);
	if (!eval_ret) {
		unreal_engine_py_log_error();
		return;
	}
	Py_DECREF(eval_ret);
}

void FUnrealEnginePythonModule::RunFile(char *filename) {
	FScopePythonGIL gil;
	char *full_path = TCHAR_TO_UTF8(*FPaths::Combine(*FPaths::GameContentDir(), UTF8_TO_TCHAR("Scripts"), *FString("/"), UTF8_TO_TCHAR(filename)));
	FILE *fd = nullptr;

#if PLATFORM_WINDOWS
	if (fopen_s(&fd, full_path, "r") != 0) {
		UE_LOG(LogPython, Error, TEXT("Unable to open file %s"), UTF8_TO_TCHAR(full_path));
		return;
	}
#else
	fd = fopen(full_path, "r");
	if (!fd) {
		UE_LOG(LogPython, Error, TEXT("Unable to open file %s"), UTF8_TO_TCHAR(full_path));
		return;
	}
#endif
	PyObject *eval_ret = PyRun_File(fd, full_path, Py_file_input, (PyObject *)main_dict, (PyObject *)local_dict);
	fclose(fd);
	if (!eval_ret) {
		unreal_engine_py_log_error();
		return;
	}
	Py_DECREF(eval_ret);
}

#undef LOCTEXT_NAMESPACE

IMPLEMENT_MODULE(FUnrealEnginePythonModule, UnrealEnginePython)

<commit_msg>support for upcoming linux dlopen() refactoring<commit_after>// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.

#include "UnrealEnginePythonPrivatePCH.h"

void unreal_engine_init_py_module();

#if UNREAL_ENGINE_PYTHON_ON_LINUX
const char *ue4_module_options = "linux_global_symbols";
#endif

#if PY_MAJOR_VERSION < 3
char *PyUnicode_AsUTF8(PyObject *py_str) {
	if (PyUnicode_Check(py_str)) {
		PyObject *unicode = PyUnicode_AsUTF8String(py_str);
		if (unicode) {
			return PyString_AsString(unicode);
		}
		// just a hack to avoid crashes
		return (char *)"<invalid_string>";
	}
	return PyString_AsString(py_str);
}
#endif

#define LOCTEXT_NAMESPACE "FUnrealEnginePythonModule"

void FUnrealEnginePythonModule::PythonGILRelease() {
#if UEPY_THREADING
	ue_python_gil = PyEval_SaveThread();
#endif
}

void FUnrealEnginePythonModule::PythonGILAcquire() {
#if UEPY_THREADING
	PyEval_RestoreThread((PyThreadState *)ue_python_gil);
#endif
}

void FUnrealEnginePythonModule::StartupModule()
{
	// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module

	Py_Initialize();
#if PY_MAJOR_VERSION >= 3
	wchar_t *argv[] = { UTF8_TO_TCHAR("UnrealEngine"), NULL };
#else
	char *argv[] = { (char *)"UnrealEngine", NULL };
#endif
	PySys_SetArgv(1, argv);

	PyEval_InitThreads();

	unreal_engine_init_py_module();

	PyObject *py_sys = PyImport_ImportModule("sys");
	PyObject *py_sys_dict = PyModule_GetDict(py_sys);

	PyObject *py_path = PyDict_GetItemString(py_sys_dict, "path");

	char *zip_path = TCHAR_TO_UTF8(*FPaths::Combine(*FPaths::GameContentDir(), UTF8_TO_TCHAR("python35.zip")));
	PyObject *py_zip_path = PyUnicode_FromString(zip_path);
	PyList_Insert(py_path, 0, py_zip_path);

	char *scripts_path = TCHAR_TO_UTF8(*FPaths::Combine(*FPaths::GameContentDir(), UTF8_TO_TCHAR("Scripts")));
	PyObject *py_scripts_path = PyUnicode_FromString(scripts_path);
	PyList_Insert(py_path, 0, py_scripts_path);

	UE_LOG(LogPython, Log, TEXT("Python VM initialized: %s"), UTF8_TO_TCHAR(Py_GetVersion()));
	UE_LOG(LogPython, Log, TEXT("Python Scripts search path: %s"), UTF8_TO_TCHAR(scripts_path));

	PyObject *main_module = PyImport_AddModule("__main__");
	main_dict = PyModule_GetDict(main_module);
	local_dict = main_dict;// PyDict_New();

	if (PyImport_ImportModule("ue_site")) {
		UE_LOG(LogPython, Log, TEXT("ue_site Python module successfully imported"));
	}
	else {
		// TODO gracefully manage the error
		unreal_engine_py_log_error();
	}

#if WITH_EDITOR
	// register commands (after importing ue_site)
	FPythonSlateCommands::Register();
	// apply extenders
	FPythonSlateCommands::ApplyPythonExtenders();
#endif

	// release the GIL
	PythonGILRelease();

}

void FUnrealEnginePythonModule::ShutdownModule()
{
	// This function may be called during shutdown to clean up your module.  For modules that support dynamic reloading,
	// we call this function before unloading the module.

	UE_LOG(LogPython, Log, TEXT("Goodbye Python"));
	PythonGILAcquire();
	Py_Finalize();
}

void FUnrealEnginePythonModule::RunString(char *str) {
	FScopePythonGIL gil;
	PyObject *eval_ret = PyRun_String(str, Py_file_input, (PyObject *)main_dict, (PyObject *)local_dict);
	if (!eval_ret) {
		unreal_engine_py_log_error();
		return;
	}
	Py_DECREF(eval_ret);
}

void FUnrealEnginePythonModule::RunFile(char *filename) {
	FScopePythonGIL gil;
	char *full_path = TCHAR_TO_UTF8(*FPaths::Combine(*FPaths::GameContentDir(), UTF8_TO_TCHAR("Scripts"), *FString("/"), UTF8_TO_TCHAR(filename)));
	FILE *fd = nullptr;

#if PLATFORM_WINDOWS
	if (fopen_s(&fd, full_path, "r") != 0) {
		UE_LOG(LogPython, Error, TEXT("Unable to open file %s"), UTF8_TO_TCHAR(full_path));
		return;
	}
#else
	fd = fopen(full_path, "r");
	if (!fd) {
		UE_LOG(LogPython, Error, TEXT("Unable to open file %s"), UTF8_TO_TCHAR(full_path));
		return;
	}
#endif
	PyObject *eval_ret = PyRun_File(fd, full_path, Py_file_input, (PyObject *)main_dict, (PyObject *)local_dict);
	fclose(fd);
	if (!eval_ret) {
		unreal_engine_py_log_error();
		return;
	}
	Py_DECREF(eval_ret);
}

#undef LOCTEXT_NAMESPACE

IMPLEMENT_MODULE(FUnrealEnginePythonModule, UnrealEnginePython)

<|endoftext|>


<commit_before>//@author A0114171W
#include "stdafx.h"
#include <CppUnitTest.h>

#include <cstdio>
#include <fstream>  // NOLINT (readability/streams)
#include "../mocks.h"
#include "internal/operations/erase_operation.h"
#include "internal/operations/post_operation.h"
#include "internal/operations/put_operation.h"
#include "internal/internal_datastore.h"
#include "exceptions/datastore_corrupt_exception.h"

using Assert = Microsoft::VisualStudio::CppUnitTestFramework::Assert;

namespace You {
namespace DataStore {
namespace UnitTests {

using DataStore = You::DataStore::Internal::DataStore;

/// Unit Test Class for \ref Internal::DataStore class
TEST_CLASS(DataStoreTest) {
public:
	void createDummyDataXml() {
		std::ofstream ofs("data.xml");
		ofs << "Lel I'm not even an xml";
	}

	TEST_METHOD_INITIALIZE(clearDataStoreState) {
		DataStore::get().document.reset();
		std::remove("data.xml");
	}

	TEST_METHOD_CLEANUP(cleanUpDataStoreState) {
		DataStore::get().document.reset();
		std::remove("data.xml");
	}

	/// Checks if DataStore::get() method adds a new transaction into
	/// the active transaction stack
	TEST_METHOD(beginTransactionAddsToTransactionStack) {
		DataStore& sut = DataStore::get();
		Assert::IsTrue(sut.transactionStack.empty());
		Transaction t(sut.begin());
		Assert::AreEqual(1U, sut.transactionStack.size());
	}

	/// Checks if post, put, and erase methods add \ref Internal::Operation
	/// objects into the active transaction's operationsQueue
	TEST_METHOD(pushedOperationsAddedToTransactionOperationsQueue) {
		DataStore& sut = DataStore::get();
		Transaction t(sut.begin());
		sut.post(TASKS_NODE, L"10", task1);
		Assert::AreEqual(1U, t->operationsQueue.size());
		sut.put(TASKS_NODE, L"10", task2);
		Assert::AreEqual(2U, t->operationsQueue.size());
		sut.erase(TASKS_NODE, L"10");
		Assert::AreEqual(3U, t->operationsQueue.size());
	}

	/// Checks if the document is only changed when a transaction is committed
	TEST_METHOD(commitChangesXmlDocumentTree) {
		DataStore& sut = DataStore::get();
		Transaction t(sut.begin());
		sut.post(TASKS_NODE, L"10", task1);

		// Note: To check if document is not changed after commit requires
		// 2 first_child()s because the first one retrieves the tasks node
		// while the second one is to check if the children of the tasks node
		// is empty

		// document must not change without commit
		Assert::IsTrue(sut.document.first_child().first_child().empty());
		t.commit();
		// document changes after commit
		Assert::IsFalse(sut.document.first_child().first_child().empty());

		Transaction t2(sut.begin());
		sut.erase(TASKS_NODE, L"10");
		// document must not change without commit
		Assert::IsFalse(sut.document.first_child().first_child().empty());
		t2.commit();
		// document changes after commit
		Assert::IsTrue(sut.document.first_child().first_child().empty());
	}

	/// Checks if rollback cleans up the transaction stack too
	TEST_METHOD(rollbackDeleteTransactionFromStack) {
		DataStore& sut = DataStore::get();
		Transaction t(sut.begin());
		Assert::AreEqual(1U, sut.transactionStack.size());
		t.rollback();
		Assert::AreEqual(0U, sut.transactionStack.size());
	}

	/// Unit test to check if getAll behaves correctly when getting from the
	/// XML document tree
	TEST_METHOD(getAllFromTree) {
		DataStore& sut = DataStore::get();

		// Create mock
		sut.document.append_child(L"tasks").append_child(L"task").
			append_child(pugi::xml_node_type::node_pcdata).set_value(L"what");

		std::vector<KeyValuePairs> result = sut.getAll(L"tasks");
		Assert::AreEqual(1U, result.size());
	}

	/// Unit test to check if getAll behaves correctly when getting from
	/// file (scenario: during load)
	TEST_METHOD(getAllFromFile) {
		DataStore& sut = DataStore::get();

		// Create mock
		sut.document.append_child(L"tasks").append_child(L"task").
			append_child(pugi::xml_node_type::node_pcdata).set_value(L"what");
		sut.document.save_file(sut.FILE_PATH.c_str());
		sut.document.reset();

		std::vector<KeyValuePairs> result = sut.getAll(L"tasks");
		Assert::AreEqual(1U, result.size());
	}

	/// Checks if saving and loading works
	TEST_METHOD(saveAndLoadTheSameThing) {
		DataStore& sut = DataStore::get();

		sut.document.append_child(L"task").
			append_child(pugi::xml_node_type::node_pcdata).set_value(L"what");
		bool result = sut.saveData();
		Assert::IsTrue(result);

		sut.document.reset();

		sut.loadData();
		std::wstring value = sut.document.child(L"task").child_value();
		Assert::AreEqual(std::wstring(L"what"), value);
	}

	/// Unit test for \ref Internal::Transaction 's push operation
	TEST_METHOD(pushOperationToTransactionWithoutDataStore) {
		Internal::Transaction sut;

		std::unique_ptr<Internal::Operation> post =
			std::make_unique<Internal::PostOperation>(TASKS_NODE, L"0", task1);
		sut.push(std::move(post));
		Assert::AreEqual(1U, sut.operationsQueue.size());

		std::unique_ptr<Internal::Operation> put =
			std::make_unique<Internal::PutOperation>(TASKS_NODE, L"0", task1);
		sut.push(std::move(put));
		Assert::AreEqual(2U, sut.operationsQueue.size());

		std::unique_ptr<Internal::Operation> erase =
			std::make_unique<Internal::EraseOperation>(TASKS_NODE, L"0");
		sut.push(std::move(erase));
		Assert::AreEqual(3U, sut.operationsQueue.size());

		sut.operationsQueue.clear();
	}

	/// Unit test for \ref Internal::Transaction 's mergeOperationsQueue method
	TEST_METHOD(mergeOperationsQueueIsAppend) {
		boost::ptr_deque<Internal::Operation> q1;
		boost::ptr_deque<Internal::Operation> q2;

		std::unique_ptr<Internal::Operation> post =
			std::make_unique<Internal::PostOperation>(TASKS_NODE, L"0", task1);
		std::unique_ptr<Internal::Operation> erase =
			std::make_unique<Internal::EraseOperation>(TASKS_NODE, L"0");
		q1.push_back(post.release());
		q2.push_back(erase.release());

		Internal::Transaction sut;
		sut.mergeOperationsQueue(q1);
		Assert::AreEqual(1U, sut.mergedOperationsQueue.size());
		sut.mergeOperationsQueue(q2);
		Assert::AreEqual(2U, sut.mergedOperationsQueue.size());

		pugi::xml_document mockDocument;

		// Check if the order of operation is correct
		bool result = sut.mergedOperationsQueue.front().run(mockDocument);
		Assert::IsTrue(result);
		result = sut.mergedOperationsQueue.back().run(mockDocument);
		Assert::IsTrue(result);
	}

	TEST_METHOD(wipeData) {
		DataStore::get().wipeData();
	}

	TEST_METHOD(firstLoadDoesNotThrowException) {
		std::remove("data.xml");
		Internal::DataStore::get().loadData();
	}

	TEST_METHOD(xmlParseErrorThrowsException) {
		createDummyDataXml();
		auto fun = [] { Internal::DataStore::get().loadData(); };
		Assert::ExpectException<NotWellFormedXmlException>(fun);
	}
};
}  // namespace UnitTests
}  // namespace DataStore
}  // namespace You
<commit_msg>Fix tests due to regression from the DataStore changes<commit_after>//@author A0114171W
#include "stdafx.h"
#include <CppUnitTest.h>

#include <cstdio>
#include <fstream>  // NOLINT (readability/streams)
#include "../mocks.h"
#include "internal/operations/erase_operation.h"
#include "internal/operations/post_operation.h"
#include "internal/operations/put_operation.h"
#include "internal/internal_datastore.h"
#include "exceptions/datastore_corrupt_exception.h"

using Assert = Microsoft::VisualStudio::CppUnitTestFramework::Assert;

namespace You {
namespace DataStore {
namespace UnitTests {

using DataStore = You::DataStore::Internal::DataStore;

/// Unit Test Class for \ref Internal::DataStore class
TEST_CLASS(DataStoreTest) {
public:
	void createDummyDataXml() {
		std::ofstream ofs("data.xml");
		ofs << "Lel I'm not even an xml";
	}

	TEST_METHOD_INITIALIZE(clearDataStoreState) {
		DataStore::get().document.reset();
		std::remove("data.xml");
		DataStore::get().loadData();
	}

	TEST_METHOD_CLEANUP(cleanUpDataStoreState) {
		DataStore::get().document.reset();
		std::remove("data.xml");
	}

	/// Checks if DataStore::get() method adds a new transaction into
	/// the active transaction stack
	TEST_METHOD(beginTransactionAddsToTransactionStack) {
		DataStore& sut = DataStore::get();
		Assert::IsTrue(sut.transactionStack.empty());
		Transaction t(sut.begin());
		Assert::AreEqual(1U, sut.transactionStack.size());
	}

	/// Checks if post, put, and erase methods add \ref Internal::Operation
	/// objects into the active transaction's operationsQueue
	TEST_METHOD(pushedOperationsAddedToTransactionOperationsQueue) {
		DataStore& sut = DataStore::get();
		Transaction t(sut.begin());
		sut.post(TASKS_NODE, L"10", task1);
		Assert::AreEqual(1U, t->operationsQueue.size());
		sut.put(TASKS_NODE, L"10", task2);
		Assert::AreEqual(2U, t->operationsQueue.size());
		sut.erase(TASKS_NODE, L"10");
		Assert::AreEqual(3U, t->operationsQueue.size());
	}

	/// Checks if the document is only changed when a transaction is committed
	TEST_METHOD(commitChangesXmlDocumentTree) {
		DataStore& sut = DataStore::get();
		Transaction t(sut.begin());
		sut.post(TASKS_NODE, L"10", task1);

		// Note: To check if document is not changed after commit requires
		// 2 first_child()s because the first one retrieves the tasks node
		// while the second one is to check if the children of the tasks node
		// is empty

		// document must not change without commit
		Assert::IsTrue(sut.root.first_child().first_child().empty());
		t.commit();
		// document changes after commit
		Assert::IsFalse(sut.root.first_child().first_child().empty());

		Transaction t2(sut.begin());
		sut.erase(TASKS_NODE, L"10");
		// document must not change without commit
		Assert::IsFalse(sut.root.first_child().first_child().empty());
		t2.commit();
		// document changes after commit
		Assert::IsTrue(sut.root.first_child().first_child().empty());
	}

	/// Checks if rollback cleans up the transaction stack too
	TEST_METHOD(rollbackDeleteTransactionFromStack) {
		DataStore& sut = DataStore::get();
		Transaction t(sut.begin());
		Assert::AreEqual(1U, sut.transactionStack.size());
		t.rollback();
		Assert::AreEqual(0U, sut.transactionStack.size());
	}

	/// Unit test to check if getAll behaves correctly when getting from the
	/// XML document tree
	TEST_METHOD(getAllFromTree) {
		DataStore& sut = DataStore::get();

		// Create mock
		sut.root.append_child(L"tasks").append_child(L"task").
			append_child(pugi::xml_node_type::node_pcdata).set_value(L"what");

		std::vector<KeyValuePairs> result = sut.getAll(L"tasks");
		Assert::AreEqual(1U, result.size());
	}

	/// Unit test to check if getAll behaves correctly when getting from
	/// file (scenario: during load)
	TEST_METHOD(getAllFromFile) {
		DataStore& sut = DataStore::get();

		// Create mock
		sut.root.append_child(L"tasks").append_child(L"task").
			append_child(pugi::xml_node_type::node_pcdata).set_value(L"what");
		sut.document.save_file(sut.FILE_PATH.c_str());
		sut.document.reset();

		std::vector<KeyValuePairs> result = sut.getAll(L"tasks");
		Assert::AreEqual(1U, result.size());
	}

	/// Checks if saving and loading works
	TEST_METHOD(saveAndLoadTheSameThing) {
		DataStore& sut = DataStore::get();

		sut.document.append_child(L"task").
			append_child(pugi::xml_node_type::node_pcdata).set_value(L"what");
		bool result = sut.saveData();
		Assert::IsTrue(result);

		sut.document.reset();

		sut.loadData();
		std::wstring value = sut.document.child(L"task").child_value();
		Assert::AreEqual(std::wstring(L"what"), value);
	}

	/// Unit test for \ref Internal::Transaction 's push operation
	TEST_METHOD(pushOperationToTransactionWithoutDataStore) {
		Internal::Transaction sut;

		std::unique_ptr<Internal::Operation> post =
			std::make_unique<Internal::PostOperation>(TASKS_NODE, L"0", task1);
		sut.push(std::move(post));
		Assert::AreEqual(1U, sut.operationsQueue.size());

		std::unique_ptr<Internal::Operation> put =
			std::make_unique<Internal::PutOperation>(TASKS_NODE, L"0", task1);
		sut.push(std::move(put));
		Assert::AreEqual(2U, sut.operationsQueue.size());

		std::unique_ptr<Internal::Operation> erase =
			std::make_unique<Internal::EraseOperation>(TASKS_NODE, L"0");
		sut.push(std::move(erase));
		Assert::AreEqual(3U, sut.operationsQueue.size());

		sut.operationsQueue.clear();
	}

	/// Unit test for \ref Internal::Transaction 's mergeOperationsQueue method
	TEST_METHOD(mergeOperationsQueueIsAppend) {
		boost::ptr_deque<Internal::Operation> q1;
		boost::ptr_deque<Internal::Operation> q2;

		std::unique_ptr<Internal::Operation> post =
			std::make_unique<Internal::PostOperation>(TASKS_NODE, L"0", task1);
		std::unique_ptr<Internal::Operation> erase =
			std::make_unique<Internal::EraseOperation>(TASKS_NODE, L"0");
		q1.push_back(post.release());
		q2.push_back(erase.release());

		Internal::Transaction sut;
		sut.mergeOperationsQueue(q1);
		Assert::AreEqual(1U, sut.mergedOperationsQueue.size());
		sut.mergeOperationsQueue(q2);
		Assert::AreEqual(2U, sut.mergedOperationsQueue.size());

		pugi::xml_document mockDocument;

		// Check if the order of operation is correct
		bool result = sut.mergedOperationsQueue.front().run(mockDocument);
		Assert::IsTrue(result);
		result = sut.mergedOperationsQueue.back().run(mockDocument);
		Assert::IsTrue(result);
	}

	TEST_METHOD(wipeData) {
		DataStore::get().wipeData();
	}

	TEST_METHOD(firstLoadDoesNotThrowException) {
		std::remove("data.xml");
		Internal::DataStore::get().loadData();
	}

	TEST_METHOD(xmlParseErrorThrowsException) {
		DataStore::get().wipeData();
		createDummyDataXml();
		auto fun = [] { Internal::DataStore::get().loadData(); };
		Assert::ExpectException<NotWellFormedXmlException>(fun);
	}
};
}  // namespace UnitTests
}  // namespace DataStore
}  // namespace You
<|endoftext|>


<commit_before>//
//  DiskII.cpp
//  Clock Signal
//
//  Created by Thomas Harte on 20/04/2018.
//  Copyright 2018 Thomas Harte. All rights reserved.
//

#include "DiskII.hpp"

#include <cstdio>
#include <cstring>

using namespace Apple;

namespace  {
	const uint8_t input_command = 0x4;	// i.e. Q6
	const uint8_t input_mode = 0x8;		// i.e. Q7
	const uint8_t input_flux = 0x1;
}

DiskII::DiskII(int clock_rate) :
	clock_rate_(clock_rate),
	inputs_(input_command),
	drives_{{clock_rate, 300, 1}, {clock_rate, 300, 1}}
{
	drives_[0].set_clocking_hint_observer(this);
	drives_[1].set_clocking_hint_observer(this);
	drives_[active_drive_].set_event_delegate(this);
}

void DiskII::set_control(Control control, bool on) {
	int previous_stepper_mask = stepper_mask_;
	switch(control) {
		case Control::P0: stepper_mask_ = (stepper_mask_ & 0xe) | (on ? 0x1 : 0x0);	break;
		case Control::P1: stepper_mask_ = (stepper_mask_ & 0xd) | (on ? 0x2 : 0x0);	break;
		case Control::P2: stepper_mask_ = (stepper_mask_ & 0xb) | (on ? 0x4 : 0x0);	break;
		case Control::P3: stepper_mask_ = (stepper_mask_ & 0x7) | (on ? 0x8 : 0x0);	break;

		case Control::Motor:
			motor_is_enabled_ = on;
			drives_[active_drive_].set_motor_on(on);
		return;
	}

	// If the stepper magnet selections have changed, and any is on, see how
	// that moves the head.
	if(previous_stepper_mask ^ stepper_mask_ && stepper_mask_) {
		// Convert from a representation of bits set to the centre of pull.
		int direction = 0;
		if(stepper_mask_&1) direction += (((stepper_position_ - 0) + 4)&7) - 4;
		if(stepper_mask_&2) direction += (((stepper_position_ - 2) + 4)&7) - 4;
		if(stepper_mask_&4) direction += (((stepper_position_ - 4) + 4)&7) - 4;
		if(stepper_mask_&8) direction += (((stepper_position_ - 6) + 4)&7) - 4;
		const int bits_set = (stepper_mask_&1) + ((stepper_mask_ >> 1)&1) + ((stepper_mask_ >> 2)&1) + ((stepper_mask_ >> 3)&1);
		direction /= bits_set;

		// Compare to the stepper position to decide whether that pulls in the current cog notch,
		// or grabs a later one.
		drives_[active_drive_].step(Storage::Disk::HeadPosition(-direction, 4));
		stepper_position_ = (stepper_position_ - direction + 8) & 7;
	}
}

void DiskII::select_drive(int drive) {
	if((drive&1) == active_drive_) return;

	drives_[active_drive_].set_event_delegate(this);
	drives_[active_drive_^1].set_event_delegate(nullptr);

	drives_[active_drive_].set_motor_on(false);
	active_drive_ = drive & 1;
	drives_[active_drive_].set_motor_on(motor_is_enabled_);
}

// The read pulse is controlled by a special IC that outputs a 1us pulse for every field reversal on the disk.

void DiskII::run_for(const Cycles cycles) {
	if(preferred_clocking() == ClockingHint::Preference::None) return;

	auto integer_cycles = cycles.as_integral();
	while(integer_cycles--) {
		const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6);
		if(flux_duration_) {
			--flux_duration_;
			if(!flux_duration_) inputs_ |= input_flux;
		}
		state_ = state_machine_[size_t(address)];
		switch(state_ & 0xf) {
			default:	shift_register_ = 0;										break;	// clear
			case 0x8:																break;	// nop

			case 0x9:	shift_register_ = uint8_t(shift_register_ << 1);			break;	// shift left, bringing in a zero
			case 0xd:	shift_register_ = uint8_t((shift_register_ << 1) | 1);		break;	// shift left, bringing in a one

			case 0xa:	// shift right, bringing in write protected status
				shift_register_ = (shift_register_ >> 1) | (is_write_protected() ? 0x80 : 0x00);

				// If the controller is in the sense write protect loop but the register will never change,
				// short circuit further work and return now.
				if(shift_register_ == (is_write_protected() ? 0xff : 0x00)) {
					if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(integer_cycles));
					if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(integer_cycles));
					decide_clocking_preference();
					return;
				}
			break;
			case 0xb:	shift_register_ = data_input_;								break;	// load data register from data bus
		}

		// Currently writing?
		if(inputs_&input_mode) {
			// state_ & 0x80 should be the current level sent to the disk;
			// therefore transitions in that bit should become flux transitions
			drives_[active_drive_].write_bit(!!((state_ ^ address) & 0x80));
		}

		// TODO: surely there's a less heavyweight solution than inline updates?
		if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(1));
		if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1));
	}

	// Per comp.sys.apple2.programmer there is a delay between the controller
	// motor switch being flipped and the drive motor actually switching off.
	// This models that, accepting overrun as a risk.
	if(motor_off_time_ >= 0) {
		motor_off_time_ -= cycles.as_integral();
		if(motor_off_time_ < 0) {
			set_control(Control::Motor, false);
		}
	}
	decide_clocking_preference();
}

void DiskII::decide_clocking_preference() {
	ClockingHint::Preference prior_preference = clocking_preference_;

	// If in read mode, clocking is either:
	//
	//	just-in-time, if drives are running or the shift register has any 1s in it or a flux event hasn't yet passed; or
	//	none, given that drives are not running, the shift register has already emptied and there's no flux about to be received.
	if(!(inputs_ & ~input_flux)) {
		clocking_preference_ = (!motor_is_enabled_ && !shift_register_ && (inputs_&input_flux)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
	}

	// If in writing mode, clocking is real time.
	if(inputs_ & input_mode) {
		clocking_preference_ = ClockingHint::Preference::RealTime;
	}

	// If in sense-write-protect mode, clocking is just-in-time if the shift register hasn't yet filled with the value that
	// corresponds to the current write protect status. Otherwise it is none.
	if((inputs_ & ~input_flux) == input_command) {
		clocking_preference_ = (shift_register_ == (is_write_protected() ? 0xff : 0x00)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
	}

	// Announce a change if there was one.
	if(prior_preference != clocking_preference_)
		update_clocking_observer();
}

bool DiskII::is_write_protected() {
	return !!(stepper_mask_ & 2) | drives_[active_drive_].get_is_read_only();
}

void DiskII::set_state_machine(const std::vector<uint8_t> &state_machine) {
	/*
		An unadulterated P6 ROM read returns values with an address formed as:

			state b0, state b2, state b3, pulse, Q7, Q6, shift, state b1

		... and has the top nibble of each value stored in the ROM reflected.
		Beneath Apple Pro-DOS uses a different order and several of the
		online copies are reformatted into that order.

		So the code below remaps into Beneath Apple Pro-DOS order if the
		supplied state machine isn't already in that order.
	*/

	if(state_machine[0] != 0x18) {
		for(size_t source_address = 0; source_address < 256; ++source_address) {
			// Remap into Beneath Apple Pro-DOS address form.
			const size_t destination_address =
				((source_address&0x20) ? 0x80 : 0x00) |
				((source_address&0x40) ? 0x40 : 0x00) |
				((source_address&0x01) ? 0x20 : 0x00) |
				((source_address&0x80) ? 0x10 : 0x00) |
				((source_address&0x08) ? 0x08 : 0x00) |
				((source_address&0x04) ? 0x04 : 0x00) |
				((source_address&0x02) ? 0x02 : 0x00) |
				((source_address&0x10) ? 0x01 : 0x00);

			// Store.
			const uint8_t source_value = state_machine[source_address];
			state_machine_[destination_address] =
				((source_value & 0x80) ? 0x10 : 0x0) |
				((source_value & 0x40) ? 0x20 : 0x0) |
				((source_value & 0x20) ? 0x40 : 0x0) |
				((source_value & 0x10) ? 0x80 : 0x0) |
				(source_value & 0x0f);
		}
	} else {
		for(size_t source_address = 0; source_address < 256; ++source_address) {
			// Reshuffle ordering of bytes only, to retain indexing by the high nibble.
			const size_t destination_address =
				((source_address&0x80) ? 0x80 : 0x00) |
				((source_address&0x40) ? 0x40 : 0x00) |
				((source_address&0x01) ? 0x20 : 0x00) |
				((source_address&0x20) ? 0x10 : 0x00) |
				((source_address&0x08) ? 0x08 : 0x00) |
				((source_address&0x04) ? 0x04 : 0x00) |
				((source_address&0x02) ? 0x02 : 0x00) |
				((source_address&0x10) ? 0x01 : 0x00);

			state_machine_[destination_address] = state_machine[source_address];
		}
	}
}

void DiskII::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive) {
	drives_[drive].set_disk(disk);
}

void DiskII::process_event(const Storage::Disk::Drive::Event &event) {
	if(event.type == Storage::Disk::Track::Event::FluxTransition) {
		inputs_ &= ~input_flux;
		flux_duration_ = 2;	// Upon detection of a flux transition, the flux flag should stay set for 1us. Emulate that as two cycles.
		decide_clocking_preference();
	}
}

void DiskII::set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) {
	drive_is_sleeping_[0] = drives_[0].preferred_clocking() == ClockingHint::Preference::None;
	drive_is_sleeping_[1] = drives_[1].preferred_clocking() == ClockingHint::Preference::None;
	decide_clocking_preference();
}

ClockingHint::Preference DiskII::preferred_clocking() const {
	return clocking_preference_;
}

void DiskII::set_data_input(uint8_t input) {
	data_input_ = input;
}

int DiskII::read_address(int address) {
	switch(address & 0xf) {
		default:
		case 0x0:	set_control(Control::P0, false);	break;
		case 0x1:	set_control(Control::P0, true);		break;
		case 0x2:	set_control(Control::P1, false);	break;
		case 0x3:	set_control(Control::P1, true);		break;
		case 0x4:	set_control(Control::P2, false);	break;
		case 0x5:	set_control(Control::P2, true);		break;
		case 0x6:	set_control(Control::P3, false);	break;
		case 0x7:	set_control(Control::P3, true);		break;

		case 0x8:
			shift_register_ = 0;
			motor_off_time_ = clock_rate_;
		break;
		case 0x9:
			set_control(Control::Motor, true);
			motor_off_time_ = -1;
		break;

		case 0xa:	select_drive(0);					break;
		case 0xb:	select_drive(1);					break;

		case 0xc:	inputs_ &= ~input_command;			break;
		case 0xd:	inputs_ |= input_command;			break;
		case 0xe:
			if(inputs_ & input_mode)
				drives_[active_drive_].end_writing();
			inputs_ &= ~input_mode;
		break;
		case 0xf:
			if(!(inputs_ & input_mode))
				drives_[active_drive_].begin_writing(Storage::Time(1, int(clock_rate_)), false);
			inputs_ |= input_mode;
		break;
	}
	decide_clocking_preference();
	return (address & 1) ? 0xff : shift_register_;
}

void DiskII::set_activity_observer(Activity::Observer *observer) {
	drives_[0].set_activity_observer(observer, "Drive 1", true);
	drives_[1].set_activity_observer(observer, "Drive 2", true);
}

Storage::Disk::Drive &DiskII::get_drive(int index) {
	return drives_[index];
}
<commit_msg>Correct Disk II sleeping test to allow for spin-down.<commit_after>//
//  DiskII.cpp
//  Clock Signal
//
//  Created by Thomas Harte on 20/04/2018.
//  Copyright 2018 Thomas Harte. All rights reserved.
//

#include "DiskII.hpp"

#include <cstdio>
#include <cstring>

using namespace Apple;

namespace  {
	const uint8_t input_command = 0x4;	// i.e. Q6
	const uint8_t input_mode = 0x8;		// i.e. Q7
	const uint8_t input_flux = 0x1;
}

DiskII::DiskII(int clock_rate) :
	clock_rate_(clock_rate),
	inputs_(input_command),
	drives_{{clock_rate, 300, 1}, {clock_rate, 300, 1}}
{
	drives_[0].set_clocking_hint_observer(this);
	drives_[1].set_clocking_hint_observer(this);
	drives_[active_drive_].set_event_delegate(this);
}

void DiskII::set_control(Control control, bool on) {
	int previous_stepper_mask = stepper_mask_;
	switch(control) {
		case Control::P0: stepper_mask_ = (stepper_mask_ & 0xe) | (on ? 0x1 : 0x0);	break;
		case Control::P1: stepper_mask_ = (stepper_mask_ & 0xd) | (on ? 0x2 : 0x0);	break;
		case Control::P2: stepper_mask_ = (stepper_mask_ & 0xb) | (on ? 0x4 : 0x0);	break;
		case Control::P3: stepper_mask_ = (stepper_mask_ & 0x7) | (on ? 0x8 : 0x0);	break;

		case Control::Motor:
			motor_is_enabled_ = on;
			drives_[active_drive_].set_motor_on(on);
		return;
	}

	// If the stepper magnet selections have changed, and any is on, see how
	// that moves the head.
	if(previous_stepper_mask ^ stepper_mask_ && stepper_mask_) {
		// Convert from a representation of bits set to the centre of pull.
		int direction = 0;
		if(stepper_mask_&1) direction += (((stepper_position_ - 0) + 4)&7) - 4;
		if(stepper_mask_&2) direction += (((stepper_position_ - 2) + 4)&7) - 4;
		if(stepper_mask_&4) direction += (((stepper_position_ - 4) + 4)&7) - 4;
		if(stepper_mask_&8) direction += (((stepper_position_ - 6) + 4)&7) - 4;
		const int bits_set = (stepper_mask_&1) + ((stepper_mask_ >> 1)&1) + ((stepper_mask_ >> 2)&1) + ((stepper_mask_ >> 3)&1);
		direction /= bits_set;

		// Compare to the stepper position to decide whether that pulls in the current cog notch,
		// or grabs a later one.
		drives_[active_drive_].step(Storage::Disk::HeadPosition(-direction, 4));
		stepper_position_ = (stepper_position_ - direction + 8) & 7;
	}
}

void DiskII::select_drive(int drive) {
	if((drive&1) == active_drive_) return;

	drives_[active_drive_].set_event_delegate(this);
	drives_[active_drive_^1].set_event_delegate(nullptr);

	drives_[active_drive_].set_motor_on(false);
	active_drive_ = drive & 1;
	drives_[active_drive_].set_motor_on(motor_is_enabled_);
}

// The read pulse is controlled by a special IC that outputs a 1us pulse for every field reversal on the disk.

void DiskII::run_for(const Cycles cycles) {
	if(preferred_clocking() == ClockingHint::Preference::None) return;

	auto integer_cycles = cycles.as_integral();
	while(integer_cycles--) {
		const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6);
		if(flux_duration_) {
			--flux_duration_;
			if(!flux_duration_) inputs_ |= input_flux;
		}
		state_ = state_machine_[size_t(address)];
		switch(state_ & 0xf) {
			default:	shift_register_ = 0;										break;	// clear
			case 0x8:																break;	// nop

			case 0x9:	shift_register_ = uint8_t(shift_register_ << 1);			break;	// shift left, bringing in a zero
			case 0xd:	shift_register_ = uint8_t((shift_register_ << 1) | 1);		break;	// shift left, bringing in a one

			case 0xa:	// shift right, bringing in write protected status
				shift_register_ = (shift_register_ >> 1) | (is_write_protected() ? 0x80 : 0x00);

				// If the controller is in the sense write protect loop but the register will never change,
				// short circuit further work and return now.
				if(shift_register_ == (is_write_protected() ? 0xff : 0x00)) {
					if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(integer_cycles));
					if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(integer_cycles));
					decide_clocking_preference();
					return;
				}
			break;
			case 0xb:	shift_register_ = data_input_;								break;	// load data register from data bus
		}

		// Currently writing?
		if(inputs_&input_mode) {
			// state_ & 0x80 should be the current level sent to the disk;
			// therefore transitions in that bit should become flux transitions
			drives_[active_drive_].write_bit(!!((state_ ^ address) & 0x80));
		}

		// TODO: surely there's a less heavyweight solution than inline updates?
		if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(1));
		if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1));
	}

	// Per comp.sys.apple2.programmer there is a delay between the controller
	// motor switch being flipped and the drive motor actually switching off.
	// This models that, accepting overrun as a risk.
	if(motor_off_time_ >= 0) {
		motor_off_time_ -= cycles.as_integral();
		if(motor_off_time_ < 0) {
			set_control(Control::Motor, false);
		}
	}
	decide_clocking_preference();
}

void DiskII::decide_clocking_preference() {
	ClockingHint::Preference prior_preference = clocking_preference_;

	// If in read mode, clocking is either:
	//
	//	just-in-time, if drives are running or the shift register has any 1s in it and shifting may occur, or a flux event hasn't yet passed; or
	//	none, given that drives are not running, the shift register has already emptied or stopped and there's no flux about to be received.
	if(!(inputs_ & ~input_flux)) {
		const bool is_stuck_at_nop =
			!flux_duration_ && state_machine_[(state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6)] == state_  && (state_ &0xf) == 0x8;

		clocking_preference_ =
			(drive_is_sleeping_[0] && drive_is_sleeping_[1] && (!shift_register_ || is_stuck_at_nop) && (inputs_&input_flux))
				? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
	}

	// If in writing mode, clocking is real time.
	if(inputs_ & input_mode) {
		clocking_preference_ = ClockingHint::Preference::RealTime;
	}

	// If in sense-write-protect mode, clocking is just-in-time if the shift register hasn't yet filled with the value that
	// corresponds to the current write protect status. Otherwise it is none.
	if((inputs_ & ~input_flux) == input_command) {
		clocking_preference_ = (shift_register_ == (is_write_protected() ? 0xff : 0x00)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
	}

	// Announce a change if there was one.
	if(prior_preference != clocking_preference_)
		update_clocking_observer();
}

bool DiskII::is_write_protected() {
	return !!(stepper_mask_ & 2) | drives_[active_drive_].get_is_read_only();
}

void DiskII::set_state_machine(const std::vector<uint8_t> &state_machine) {
	/*
		An unadulterated P6 ROM read returns values with an address formed as:

			state b0, state b2, state b3, pulse, Q7, Q6, shift, state b1

		... and has the top nibble of each value stored in the ROM reflected.
		Beneath Apple Pro-DOS uses a different order and several of the
		online copies are reformatted into that order.

		So the code below remaps into Beneath Apple Pro-DOS order if the
		supplied state machine isn't already in that order.
	*/

	if(state_machine[0] != 0x18) {
		for(size_t source_address = 0; source_address < 256; ++source_address) {
			// Remap into Beneath Apple Pro-DOS address form.
			const size_t destination_address =
				((source_address&0x20) ? 0x80 : 0x00) |
				((source_address&0x40) ? 0x40 : 0x00) |
				((source_address&0x01) ? 0x20 : 0x00) |
				((source_address&0x80) ? 0x10 : 0x00) |
				((source_address&0x08) ? 0x08 : 0x00) |
				((source_address&0x04) ? 0x04 : 0x00) |
				((source_address&0x02) ? 0x02 : 0x00) |
				((source_address&0x10) ? 0x01 : 0x00);

			// Store.
			const uint8_t source_value = state_machine[source_address];
			state_machine_[destination_address] =
				((source_value & 0x80) ? 0x10 : 0x0) |
				((source_value & 0x40) ? 0x20 : 0x0) |
				((source_value & 0x20) ? 0x40 : 0x0) |
				((source_value & 0x10) ? 0x80 : 0x0) |
				(source_value & 0x0f);
		}
	} else {
		for(size_t source_address = 0; source_address < 256; ++source_address) {
			// Reshuffle ordering of bytes only, to retain indexing by the high nibble.
			const size_t destination_address =
				((source_address&0x80) ? 0x80 : 0x00) |
				((source_address&0x40) ? 0x40 : 0x00) |
				((source_address&0x01) ? 0x20 : 0x00) |
				((source_address&0x20) ? 0x10 : 0x00) |
				((source_address&0x08) ? 0x08 : 0x00) |
				((source_address&0x04) ? 0x04 : 0x00) |
				((source_address&0x02) ? 0x02 : 0x00) |
				((source_address&0x10) ? 0x01 : 0x00);

			state_machine_[destination_address] = state_machine[source_address];
		}
	}
}

void DiskII::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive) {
	drives_[drive].set_disk(disk);
}

void DiskII::process_event(const Storage::Disk::Drive::Event &event) {
	if(event.type == Storage::Disk::Track::Event::FluxTransition) {
		inputs_ &= ~input_flux;
		flux_duration_ = 2;	// Upon detection of a flux transition, the flux flag should stay set for 1us. Emulate that as two cycles.
		decide_clocking_preference();
	}
}

void DiskII::set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) {
	drive_is_sleeping_[0] = drives_[0].preferred_clocking() == ClockingHint::Preference::None;
	drive_is_sleeping_[1] = drives_[1].preferred_clocking() == ClockingHint::Preference::None;
	decide_clocking_preference();
}

ClockingHint::Preference DiskII::preferred_clocking() const {
	return clocking_preference_;
}

void DiskII::set_data_input(uint8_t input) {
	data_input_ = input;
}

int DiskII::read_address(int address) {
	switch(address & 0xf) {
		default:
		case 0x0:	set_control(Control::P0, false);	break;
		case 0x1:	set_control(Control::P0, true);		break;
		case 0x2:	set_control(Control::P1, false);	break;
		case 0x3:	set_control(Control::P1, true);		break;
		case 0x4:	set_control(Control::P2, false);	break;
		case 0x5:	set_control(Control::P2, true);		break;
		case 0x6:	set_control(Control::P3, false);	break;
		case 0x7:	set_control(Control::P3, true);		break;

		case 0x8:
			shift_register_ = 0;
			motor_off_time_ = clock_rate_;
		break;
		case 0x9:
			set_control(Control::Motor, true);
			motor_off_time_ = -1;
		break;

		case 0xa:	select_drive(0);					break;
		case 0xb:	select_drive(1);					break;

		case 0xc:	inputs_ &= ~input_command;			break;
		case 0xd:	inputs_ |= input_command;			break;
		case 0xe:
			if(inputs_ & input_mode)
				drives_[active_drive_].end_writing();
			inputs_ &= ~input_mode;
		break;
		case 0xf:
			if(!(inputs_ & input_mode))
				drives_[active_drive_].begin_writing(Storage::Time(1, int(clock_rate_)), false);
			inputs_ |= input_mode;
		break;
	}
	decide_clocking_preference();
	return (address & 1) ? 0xff : shift_register_;
}

void DiskII::set_activity_observer(Activity::Observer *observer) {
	drives_[0].set_activity_observer(observer, "Drive 1", true);
	drives_[1].set_activity_observer(observer, "Drive 2", true);
}

Storage::Disk::Drive &DiskII::get_drive(int index) {
	return drives_[index];
}
<|endoftext|>