<commit_before>/*
 *  MovingDots.cpp
 *  MovingDots
 *
 *  Created by <NAME> on 8/6/10.
 *  Copyright 2010 MIT. All rights reserved.
 *
 */

#include "MovingDots.h"

using namespace mw::aapl_math_utilities;


BEGIN_NAMESPACE_MW


const std::string MovingDots::FIELD_RADIUS("field_radius");
const std::string MovingDots::FIELD_CENTER_X("field_center_x");
const std::string MovingDots::FIELD_CENTER_Y("field_center_y");
const std::string MovingDots::DOT_DENSITY("dot_density");
const std::string MovingDots::DOT_SIZE("dot_size");
const std::string MovingDots::COLOR("color");
const std::string MovingDots::ALPHA_MULTIPLIER("alpha_multiplier");
const std::string MovingDots::DIRECTION("direction");
const std::string MovingDots::SPEED("speed");
const std::string MovingDots::COHERENCE("coherence");
const std::string MovingDots::LIFETIME("lifetime");
const std::string MovingDots::RAND_SEED("rand_seed");
const std::string MovingDots::MAX_NUM_DOTS("max_num_dots");
const std::string MovingDots::ANNOUNCE_DOTS("announce_dots");


void MovingDots::describeComponent(ComponentInfo &info) {
    MovingDotsBase::describeComponent(info);
    
    info.setSignature("stimulus/moving_dots");
    
    info.addParameter(FIELD_RADIUS);
    info.addParameter(FIELD_CENTER_X, "0.0");
    info.addParameter(FIELD_CENTER_Y, "0.0");
    info.addParameter(DOT_DENSITY);
    info.addParameter(DOT_SIZE);
    info.addParameter(COLOR, "1.0,1.0,1.0");
    info.addParameter(ALPHA_MULTIPLIER, "1.0");
    info.addParameter(DIRECTION, "0.0");
    info.addParameter(SPEED);
    info.addParameter(COHERENCE, "1.0");
    info.addParameter(LIFETIME, "0.0");
    info.addParameter(RAND_SEED, false);
    info.addParameter(MAX_NUM_DOTS, false);
    info.addParameter(ANNOUNCE_DOTS, "NO");
}


MovingDots::MovingDots(const ParameterValueMap &parameters) :
    MovingDotsBase(parameters),
    fieldRadius(registerVariable(parameters[FIELD_RADIUS])),
    fieldCenterX(registerVariable(parameters[FIELD_CENTER_X])),
    fieldCenterY(registerVariable(parameters[FIELD_CENTER_Y])),
    dotDensity(registerVariable(parameters[DOT_DENSITY])),
    dotSize(registerVariable(parameters[DOT_SIZE])),
    alpha(registerVariable(parameters[ALPHA_MULTIPLIER])),
    direction(registerVariable(parameters[DIRECTION])),
    speed(registerVariable(parameters[SPEED])),
    coherence(registerVariable(parameters[COHERENCE])),
    lifetime(registerVariable(parameters[LIFETIME])),
    randSeed(optionalVariable(parameters[RAND_SEED])),
    maxNumDots(optionalVariable(parameters[MAX_NUM_DOTS])),
    announceDots(parameters[ANNOUNCE_DOTS]),
    renderPipelineState(nil),
    bufferPool(nil)
{
    ParsedColorTrio color(parameters[COLOR]);
    red = registerVariable(color.getR());
    green = registerVariable(color.getG());
    blue = registerVariable(color.getB());
}


MovingDots::~MovingDots() {
    @autoreleasepool {
        bufferPool = nil;
        renderPipelineState = nil;
    }
}


Datum MovingDots::getCurrentAnnounceDrawData() {
    boost::mutex::scoped_lock locker(stim_lock);
    
    auto announceData = MovingDotsBase::getCurrentAnnounceDrawData();
    
    announceData.addElement(STIM_TYPE, "moving_dots");
    announceData.addElement(FIELD_RADIUS, currentFieldRadius);
    announceData.addElement(FIELD_CENTER_X, currentFieldCenterX);
    announceData.addElement(FIELD_CENTER_Y, currentFieldCenterY);
    announceData.addElement(DOT_DENSITY, currentDotDensity);
    announceData.addElement(DOT_SIZE, currentDotSize);
    announceData.addElement(STIM_COLOR_R, currentRed);
    announceData.addElement(STIM_COLOR_G, currentGreen);
    announceData.addElement(STIM_COLOR_B, currentBlue);
    announceData.addElement(ALPHA_MULTIPLIER, currentAlpha);
    announceData.addElement(DIRECTION, currentDirection);
    announceData.addElement(SPEED, currentSpeed);
    announceData.addElement(COHERENCE, currentCoherence);
    announceData.addElement(LIFETIME, currentLifetime);
    announceData.addElement(RAND_SEED, currentRandSeed);
    
    announceData.addElement("num_dots", long(currentNumDots));
    if (announceDots->getValue().getBool()) {
        Datum dotsData(reinterpret_cast<char *>(dotPositions.get()), getDotPositionsSize(currentNumDots));
        announceData.addElement("dots", std::move(dotsData));
    }
    
    return announceData;
}


void MovingDots::loadMetal(MetalDisplay &display) {
    boost::mutex::scoped_lock locker(stim_lock);
    
    //
    // Determine maximum number of dots
    //
    {
        if (maxNumDots) {
            currentMaxNumDots = maxNumDots->getValue().getInteger();
            if (currentMaxNumDots < 1) {
                throw SimpleException(M_DISPLAY_MESSAGE_DOMAIN, "Maximum number of dots must be greater than zero");
            }
        } else {
            double newFieldRadius = 0.0;
            double newDotDensity = 0.0;
            if (!computeNumDots(newFieldRadius, newDotDensity, currentMaxNumDots)) {
                throw SimpleException(M_DISPLAY_MESSAGE_DOMAIN, "Cannot compute maximum number of dots");
            }
        }
        
        dotPositions.reset(new simd::float2[currentMaxNumDots]);
        dotDirections.reset(new float[currentMaxNumDots]);
        dotAges.reset(new float[currentMaxNumDots]);
    }
    
    //
    // Seed the random number generator
    //
    if (randSeed) {
        currentRandSeed = randSeed->getValue().getInteger();
    } else {
        currentRandSeed = Clock::instance()->getSystemTimeNS();
    }
    randGen.seed(currentRandSeed);
    
    //
    // Compute conversion from dot size to pixels
    //
    {
        double xMin, xMax, yMin, yMax;
        display.getDisplayBounds(xMin, xMax, yMin, yMax);
        dotSizeToPixels = double(display.getMainView().drawableSize.width) / (xMax - xMin);
    }
    
    //
    // Create render pipeline state
    //
    {
        auto library = loadDefaultLibrary(display, MWORKS_GET_CURRENT_BUNDLE());
        auto vertexFunction = loadShaderFunction(library, "vertexShader");
        auto fragmentFunction = loadShaderFunction(library, "fragmentShader");
        
        auto renderPipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
        renderPipelineDescriptor.vertexFunction = vertexFunction;
        renderPipelineDescriptor.fragmentFunction = fragmentFunction;
        
        auto colorAttachment = renderPipelineDescriptor.colorAttachments[0];
        colorAttachment.pixelFormat = display.getMetalFramebufferTexturePixelFormat();
        colorAttachment.blendingEnabled = YES;
        colorAttachment.sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
        colorAttachment.destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
        colorAttachment.sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha;
        colorAttachment.destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
        
        renderPipelineState = createRenderPipelineState(display, renderPipelineDescriptor);
    }
    
    //
    // Create buffer pool to employ triple buffering, as recommended in the Metal Best Practices Guide:
    // https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/TripleBuffering.html
    //
    {<commit_msg>MovingDots now marks vertex buffers as immutable<commit_after>/*
 *  MovingDots.cpp
 *  MovingDots
 *
 *  Created by <NAME> on 8/6/10.
 *  Copyright 2010 MIT. All rights reserved.
 *
 */

#include "MovingDots.h"

using namespace mw::aapl_math_utilities;


BEGIN_NAMESPACE_MW


const std::string MovingDots::FIELD_RADIUS("field_radius");
const std::string MovingDots::FIELD_CENTER_X("field_center_x");
const std::string MovingDots::FIELD_CENTER_Y("field_center_y");
const std::string MovingDots::DOT_DENSITY("dot_density");
const std::string MovingDots::DOT_SIZE("dot_size");
const std::string MovingDots::COLOR("color");
const std::string MovingDots::ALPHA_MULTIPLIER("alpha_multiplier");
const std::string MovingDots::DIRECTION("direction");
const std::string MovingDots::SPEED("speed");
const std::string MovingDots::COHERENCE("coherence");
const std::string MovingDots::LIFETIME("lifetime");
const std::string MovingDots::RAND_SEED("rand_seed");
const std::string MovingDots::MAX_NUM_DOTS("max_num_dots");
const std::string MovingDots::ANNOUNCE_DOTS("announce_dots");


void MovingDots::describeComponent(ComponentInfo &info) {
    MovingDotsBase::describeComponent(info);
    
    info.setSignature("stimulus/moving_dots");
    
    info.addParameter(FIELD_RADIUS);
    info.addParameter(FIELD_CENTER_X, "0.0");
    info.addParameter(FIELD_CENTER_Y, "0.0");
    info.addParameter(DOT_DENSITY);
    info.addParameter(DOT_SIZE);
    info.addParameter(COLOR, "1.0,1.0,1.0");
    info.addParameter(ALPHA_MULTIPLIER, "1.0");
    info.addParameter(DIRECTION, "0.0");
    info.addParameter(SPEED);
    info.addParameter(COHERENCE, "1.0");
    info.addParameter(LIFETIME, "0.0");
    info.addParameter(RAND_SEED, false);
    info.addParameter(MAX_NUM_DOTS, false);
    info.addParameter(ANNOUNCE_DOTS, "NO");
}


MovingDots::MovingDots(const ParameterValueMap &parameters) :
    MovingDotsBase(parameters),
    fieldRadius(registerVariable(parameters[FIELD_RADIUS])),
    fieldCenterX(registerVariable(parameters[FIELD_CENTER_X])),
    fieldCenterY(registerVariable(parameters[FIELD_CENTER_Y])),
    dotDensity(registerVariable(parameters[DOT_DENSITY])),
    dotSize(registerVariable(parameters[DOT_SIZE])),
    alpha(registerVariable(parameters[ALPHA_MULTIPLIER])),
    direction(registerVariable(parameters[DIRECTION])),
    speed(registerVariable(parameters[SPEED])),
    coherence(registerVariable(parameters[COHERENCE])),
    lifetime(registerVariable(parameters[LIFETIME])),
    randSeed(optionalVariable(parameters[RAND_SEED])),
    maxNumDots(optionalVariable(parameters[MAX_NUM_DOTS])),
    announceDots(parameters[ANNOUNCE_DOTS]),
    renderPipelineState(nil),
    bufferPool(nil)
{
    ParsedColorTrio color(parameters[COLOR]);
    red = registerVariable(color.getR());
    green = registerVariable(color.getG());
    blue = registerVariable(color.getB());
}


MovingDots::~MovingDots() {
    @autoreleasepool {
        bufferPool = nil;
        renderPipelineState = nil;
    }
}


Datum MovingDots::getCurrentAnnounceDrawData() {
    boost::mutex::scoped_lock locker(stim_lock);
    
    auto announceData = MovingDotsBase::getCurrentAnnounceDrawData();
    
    announceData.addElement(STIM_TYPE, "moving_dots");
    announceData.addElement(FIELD_RADIUS, currentFieldRadius);
    announceData.addElement(FIELD_CENTER_X, currentFieldCenterX);
    announceData.addElement(FIELD_CENTER_Y, currentFieldCenterY);
    announceData.addElement(DOT_DENSITY, currentDotDensity);
    announceData.addElement(DOT_SIZE, currentDotSize);
    announceData.addElement(STIM_COLOR_R, currentRed);
    announceData.addElement(STIM_COLOR_G, currentGreen);
    announceData.addElement(STIM_COLOR_B, currentBlue);
    announceData.addElement(ALPHA_MULTIPLIER, currentAlpha);
    announceData.addElement(DIRECTION, currentDirection);
    announceData.addElement(SPEED, currentSpeed);
    announceData.addElement(COHERENCE, currentCoherence);
    announceData.addElement(LIFETIME, currentLifetime);
    announceData.addElement(RAND_SEED, currentRandSeed);
    
    announceData.addElement("num_dots", long(currentNumDots));
    if (announceDots->getValue().getBool()) {
        Datum dotsData(reinterpret_cast<char *>(dotPositions.get()), getDotPositionsSize(currentNumDots));
        announceData.addElement("dots", std::move(dotsData));
    }
    
    return announceData;
}


void MovingDots::loadMetal(MetalDisplay &display) {
    boost::mutex::scoped_lock locker(stim_lock);
    
    //
    // Determine maximum number of dots
    //
    {
        if (maxNumDots) {
            currentMaxNumDots = maxNumDots->getValue().getInteger();
            if (currentMaxNumDots < 1) {
                throw SimpleException(M_DISPLAY_MESSAGE_DOMAIN, "Maximum number of dots must be greater than zero");
            }
        } else {
            double newFieldRadius = 0.0;
            double newDotDensity = 0.0;
            if (!computeNumDots(newFieldRadius, newDotDensity, currentMaxNumDots)) {
                throw SimpleException(M_DISPLAY_MESSAGE_DOMAIN, "Cannot compute maximum number of dots");
            }
        }
        
        dotPositions.reset(new simd::float2[currentMaxNumDots]);
        dotDirections.reset(new float[currentMaxNumDots]);
        dotAges.reset(new float[currentMaxNumDots]);
    }
    
    //
    // Seed the random number generator
    //
    if (randSeed) {
        currentRandSeed = randSeed->getValue().getInteger();
    } else {
        currentRandSeed = Clock::instance()->getSystemTimeNS();
    }
    randGen.seed(currentRandSeed);
    
    //
    // Compute conversion from dot size to pixels
    //
    {
        double xMin, xMax, yMin, yMax;
        display.getDisplayBounds(xMin, xMax, yMin, yMax);
        dotSizeToPixels = double(display.getMainView().drawableSize.width) / (xMax - xMin);
    }
    
    //
    // Create render pipeline state
    //
    {
        auto library = loadDefaultLibrary(display, MWORKS_GET_CURRENT_BUNDLE());
        auto vertexFunction = loadShaderFunction(library, "vertexShader");
        auto fragmentFunction = loadShaderFunction(library, "fragmentShader");
        
        auto renderPipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
        renderPipelineDescriptor.vertexFunction = vertexFunction;
        renderPipelineDescriptor.fragmentFunction = fragmentFunction;
        
        // Mark vertex buffers as immutable, as recommended at
        // https://developer.apple.com/documentation/metal/synchronization/synchronizing_cpu_and_gpu_work
        renderPipelineDescriptor.vertexBuffers[0].mutability = MTLMutabilityImmutable;
        
        auto colorAttachment = renderPipelineDescriptor.colorAttachments[0];
        colorAttachment.pixelFormat = display.getMetalFramebufferTexturePixelFormat();
        colorAttachment.blendingEnabled = YES;
        colorAttachment.sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
        colorAttachment.destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
        colorAttachment.sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha;
        colorAttachment.destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
        
        renderPipelineState = createRenderPipelineState(display, renderPipelineDescriptor);
    }
    
    //
    // Create buffer pool to employ triple buffering, as recommended in the Metal Best Practices Guide:
    // https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/TripleBuffering.html
    //
    {
{'fraction_non_alphanumeric': 0.11028315946348734, 'fraction_numerical': 0.004674163392494242, 'mean_word_length': 4.356676342525399, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 15, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1443147', 'n_tokens_mistral': 4585, 'n_tokens_neox': 4484, 'n_words': 655}