Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions config/settings.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
TODO_plugin_name:
plugin_name_enabled:
plugins:
rollmaster_enabled:
default: false
client: true
90 changes: 90 additions & 0 deletions lib/rollmaster/dice_engine.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# frozen_string_literal: true

require "mini_racer"

module Rollmaster
class DiceEngine
@mutex = Mutex.new
@ctx_init = Mutex.new
@ctx = nil

def self.protect
rval = nil
@mutex.synchronize { rval = yield }
rval
end

def self.roll(*diceRolls)
result = nil
protect do
context = v8
result = context.call("roll", *diceRolls)
end
result
end

def self.attach_function(ctx)
ctx.eval <<~JS
function roll(...diceRolls) {
const roller = new rpgDiceRoller.DiceRoller;
roller.roll(...diceRolls);
return JSON.parse(JSON.stringify(roller.log))
}
JS
end

def self.create_context
# Create a new v8 context. Not exactly happy with starting a new v8 context, but we don't
# want to share the context between threads. Similarly, no auto disposal mechanism, so we
# just need to hope the improved user experience is worth the memory usage.
ctx = MiniRacer::Context.new(timeout: 25_000, ensure_gc_after_idle: 2000)

ctx.eval("window = globalThis; window.devicePixelRatio = 2;") # hack to make code think stuff is retina

ctx.attach("rails.logger.info", proc { |err| Rails.logger.info(err.to_s) })
ctx.attach("rails.logger.warn", proc { |err| Rails.logger.warn(err.to_s) })
ctx.attach("rails.logger.error", proc { |err| Rails.logger.error(err.to_s) })
ctx.eval <<~JS
console = {
prefix: "[Rollmaster] ",
log: function(...args){ rails.logger.info(console.prefix + args.join(" ")); },
warn: function(...args){ rails.logger.warn(console.prefix + args.join(" ")); },
error: function(...args){ rails.logger.error(console.prefix + args.join(" ")); }
}
JS

# skip transpiler. pre-compiled to UMD, which should cover modern browsers.
# See https://github.com/dice-roller/rpg-dice-roller/blob/develop/.babelrc
ctx.load("#{Rails.root}/plugins/rollmaster/public/vendors/math.js")
ctx.load("#{Rails.root}/plugins/rollmaster/public/vendors/random-js.min.js")
ctx.load("#{Rails.root}/plugins/rollmaster/public/vendors/rpg-dice-roller.min.js")

attach_function(ctx)

ctx
end

def self.v8
return @ctx if @ctx

@ctx_init.synchronize do
return @ctx if @ctx
@ctx = create_context
end
@ctx
end

def self.reset_context
@ctx_init.synchronize do
@ctx&.dispose
@ctx = nil
end
end

def self.execute_in_context(context, script)
context.eval(script)
rescue MiniRacer::RuntimeError => e
raise "Error executing script: #{e.message}"
end
end
end
9 changes: 9 additions & 0 deletions lib/rollmaster/handle_cooked_post_process.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

module ::Rollmaster
class HandleCookedPostProcess
def self.process(doc, post)
# Add your processing logic here
end
end
end
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"scripts": {
"copy-vendor": "node -e \"require('fs').copyFileSync('node_modules/@dice-roller/rpg-dice-roller/lib/esm/bundle.min.js', 'public/rpg-dice-roller/bundle.min.js')\""
"vendors": "node scripts/copy-engines-to-public.js"
},
"devDependencies": {
"@discourse/lint-configs": "2.12.0",
Expand All @@ -18,6 +18,8 @@
},
"packageManager": "pnpm@9.15.5",
"dependencies": {
"@dice-roller/rpg-dice-roller": "^5.5.1"
"@dice-roller/rpg-dice-roller": "^5.5.1",
"mathjs": "^14.2.0",
"random-js": "^2.1.0"
}
}
7 changes: 6 additions & 1 deletion plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@
enabled_site_setting :rollmaster_enabled

module ::Rollmaster
PLUGIN_NAME = "discourse-rollmaster"
PLUGIN_NAME = "rollmaster"
end

require_relative "lib/rollmaster/engine"

after_initialize do
# Code which should run after Rails has finished booting
# # I don't think this is needed, but it doesn't hurt to be safe
# PrettyText.reset_context()

on(:post_process_cooked) { |doc, post| ::Rollmaster::HandleCookedPostProcess.process(doc, post) }
# TODO: consider :chat_message_processed as well
end
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Empty file removed public/rpg-dice-roller/.gitkeep
Empty file.
10 changes: 0 additions & 10 deletions public/rpg-dice-roller/bundle.min.js

This file was deleted.

5 changes: 5 additions & 0 deletions public/vendors/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Vendors

These are node_module files that are copied over to the public folder. These are loaded into the server side.

To add files, edit [/scripts/copy-engines-to-public.js](/scripts/copy-engines-to-public.js) and run `pnpm run vendors`
3 changes: 3 additions & 0 deletions public/vendors/math.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions public/vendors/random-js.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions public/vendors/rpg-dice-roller.min.js

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions scripts/copy-engines-to-public.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const { copyFileSync } = require("node:fs");

copyFileSync(
"node_modules/mathjs/lib/browser/math.js",
"public/vendors/math.js"
);
copyFileSync(
"node_modules/random-js/dist/random-js.umd.min.js",
"public/vendors/random-js.min.js"
);
copyFileSync(
"node_modules/@dice-roller/rpg-dice-roller/lib/umd/bundle.min.js",
"public/vendors/rpg-dice-roller.min.js"
);
20 changes: 20 additions & 0 deletions scripts/debug.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* This file is designed to allow devs to debug rpg-dice-roller in a standalone nodejs environment.
* Users are advised to start a new nodejs process with the command:
*
* node ./scripts/debug.js
*
* This will start a new nodejs REPL with the rpg-dice-roller library loaded.
*/
const repl = require("node:repl");
const { DiceRoller } = require("@dice-roller/rpg-dice-roller");

function roll(...diceRolls) {
const roller = new DiceRoller();
roller.roll(...diceRolls);
return roller.log;
}

const r = repl.start({ prompt: "> " });
r.context.roll = roll;
r.context.DiceRoller = DiceRoller;
Empty file removed spec/.gitkeep
Empty file.
72 changes: 72 additions & 0 deletions spec/lib/rollmaster/dice_engine_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# frozen_string_literal: true

RSpec.configure { |c| c.filter_run_when_matching :focus }

RSpec.describe Rollmaster::DiceEngine do
describe ".roll" do
it "executes a dice roll and returns the result" do
dice_rolls = %w[2d6 1d20]
result = described_class.roll(*dice_rolls)

expect(result).not_to be_nil
expect(result).to be_a(Array)
expect(result.size).to eq(dice_rolls.size)
expect(result.all? { |r| r.is_a?(Hash) }).to be(true)
end
end

describe ".reset_context" do
it "resets the V8 context" do
initial_context = described_class.v8
described_class.reset_context
new_context = described_class.v8

expect(new_context).not_to eq(initial_context)
end
end

describe ".execute_in_context" do
it "executes a script in the given context" do
context = described_class.v8
script = "2 + 2"
result = described_class.execute_in_context(context, script)

expect(result).to eq(4)
end

it "raises an error for invalid scripts" do
context = described_class.v8
script = "invalid_code()"

expect { described_class.execute_in_context(context, script) }.to raise_error(
RuntimeError,
/Error executing script/,
)
end
end

describe ".create_context" do
it "creates a new V8 context with the expected configuration" do
context = described_class.create_context

expect(context).not_to be_nil
expect(context).to respond_to(:eval)
end

it "attaches the dice roller functions to the context" do
context = described_class.create_context

expect(context.eval("typeof rpgDiceRoller")).not_to be_nil
end
end

xdescribe ".attach_function" do
it "attaches the roll function to the context" do
context = MiniRacer::Context.new
described_class.attach_function(context)

result = context.eval("typeof roll")
expect(result).to eq("function")
end
end
end
7 changes: 7 additions & 0 deletions spec/system/core_features_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

RSpec.describe "Core features", type: :system do
before { enable_current_plugin }

it_behaves_like "having working core features"
end