├── .formatter.exs
├── .github
└── FUNDING.yml
├── .gitignore
├── .gitpod.yml
├── LICENSE.md
├── README.md
├── lib
├── pcg32.ex
└── pcg32_nif.ex
├── mix.exs
├── mix.lock
├── native
└── mystery_soup
│ ├── .cargo
│ └── config
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ └── lib.rs
└── test
├── mystery_soup_test.exs
└── test_helper.exs
/.formatter.exs:
--------------------------------------------------------------------------------
1 | # Used by "mix format"
2 | [
3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
4 | ]
5 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: khionu
4 | patreon: khionu
5 | ko_fi: khionu
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # The directory Mix will write compiled artifacts to.
2 | /_build/
3 |
4 | # If you run "mix test --cover", coverage assets end up here.
5 | /cover/
6 |
7 | # The directory Mix downloads your dependencies sources to.
8 | /deps/
9 |
10 | # Where third-party dependencies like ExDoc output generated docs.
11 | /doc/
12 |
13 | # Ignore .fetch files in case you like to edit your project deps locally.
14 | /.fetch
15 |
16 | # If the VM crashes, it generates a dump, let's ignore it too.
17 | erl_crash.dump
18 |
19 | # Also ignore archive artifacts (built via "mix archive.build").
20 | *.ez
21 |
22 | # Ignore package tarball (built via "mix hex.build").
23 | mystery_soup-*.tar
24 |
25 | # Rust stuff
26 | /native/mystery_soup/target/
27 | /native/mystery_soup/Cargo.lock
28 | priv/native/libmystery_soup.so
29 |
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | image: vborja/asdf-ubuntu:latest
2 | tasks:
3 | - init: |
4 | sudo apt-get -y install build-essential autoconf libncurses5-dev libssh-dev xsltproc fop ibwxgtk3.0-dev libgl1-mesa-dev libglu1-mesa-dev libpng-dev zip unzip
5 | asdf plugin add erlang && asdf plugin add elixir
6 | asdf install erlang 24.1.2 && asdf global erlang 24.1.2
7 | asdf install elixir 1.12.3-otp-24 && asdf global elixir 1.12.3-otp-24
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Apache License
2 | ==============
3 |
4 | _Version 2.0, January 2004_
5 | _<>_
6 |
7 | ### Terms and Conditions for use, reproduction, and distribution
8 |
9 | #### 1. Definitions
10 |
11 | “License” shall mean the terms and conditions for use, reproduction, and
12 | distribution as defined by Sections 1 through 9 of this document.
13 |
14 | “Licensor” shall mean the copyright owner or entity authorized by the copyright
15 | owner that is granting the License.
16 |
17 | “Legal Entity” shall mean the union of the acting entity and all other entities
18 | that control, are controlled by, or are under common control with that entity.
19 | For the purposes of this definition, “control” means **(i)** the power, direct or
20 | indirect, to cause the direction or management of such entity, whether by
21 | contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
22 | outstanding shares, or **(iii)** beneficial ownership of such entity.
23 |
24 | “You” (or “Your”) shall mean an individual or Legal Entity exercising
25 | permissions granted by this License.
26 |
27 | “Source” form shall mean the preferred form for making modifications, including
28 | but not limited to software source code, documentation source, and configuration
29 | files.
30 |
31 | “Object” form shall mean any form resulting from mechanical transformation or
32 | translation of a Source form, including but not limited to compiled object code,
33 | generated documentation, and conversions to other media types.
34 |
35 | “Work” shall mean the work of authorship, whether in Source or Object form, made
36 | available under the License, as indicated by a copyright notice that is included
37 | in or attached to the work (an example is provided in the Appendix below).
38 |
39 | “Derivative Works” shall mean any work, whether in Source or Object form, that
40 | is based on (or derived from) the Work and for which the editorial revisions,
41 | annotations, elaborations, or other modifications represent, as a whole, an
42 | original work of authorship. For the purposes of this License, Derivative Works
43 | shall not include works that remain separable from, or merely link (or bind by
44 | name) to the interfaces of, the Work and Derivative Works thereof.
45 |
46 | “Contribution” shall mean any work of authorship, including the original version
47 | of the Work and any modifications or additions to that Work or Derivative Works
48 | thereof, that is intentionally submitted to Licensor for inclusion in the Work
49 | by the copyright owner or by an individual or Legal Entity authorized to submit
50 | on behalf of the copyright owner. For the purposes of this definition,
51 | “submitted” means any form of electronic, verbal, or written communication sent
52 | to the Licensor or its representatives, including but not limited to
53 | communication on electronic mailing lists, source code control systems, and
54 | issue tracking systems that are managed by, or on behalf of, the Licensor for
55 | the purpose of discussing and improving the Work, but excluding communication
56 | that is conspicuously marked or otherwise designated in writing by the copyright
57 | owner as “Not a Contribution.”
58 |
59 | “Contributor” shall mean Licensor and any individual or Legal Entity on behalf
60 | of whom a Contribution has been received by Licensor and subsequently
61 | incorporated within the Work.
62 |
63 | #### 2. Grant of Copyright License
64 |
65 | Subject to the terms and conditions of this License, each Contributor hereby
66 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
67 | irrevocable copyright license to reproduce, prepare Derivative Works of,
68 | publicly display, publicly perform, sublicense, and distribute the Work and such
69 | Derivative Works in Source or Object form.
70 |
71 | #### 3. Grant of Patent License
72 |
73 | Subject to the terms and conditions of this License, each Contributor hereby
74 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
75 | irrevocable (except as stated in this section) patent license to make, have
76 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where
77 | such license applies only to those patent claims licensable by such Contributor
78 | that are necessarily infringed by their Contribution(s) alone or by combination
79 | of their Contribution(s) with the Work to which such Contribution(s) was
80 | submitted. If You institute patent litigation against any entity (including a
81 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a
82 | Contribution incorporated within the Work constitutes direct or contributory
83 | patent infringement, then any patent licenses granted to You under this License
84 | for that Work shall terminate as of the date such litigation is filed.
85 |
86 | #### 4. Redistribution
87 |
88 | You may reproduce and distribute copies of the Work or Derivative Works thereof
89 | in any medium, with or without modifications, and in Source or Object form,
90 | provided that You meet the following conditions:
91 |
92 | * **(a)** You must give any other recipients of the Work or Derivative Works a copy of
93 | this License; and
94 | * **(b)** You must cause any modified files to carry prominent notices stating that You
95 | changed the files; and
96 | * **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
97 | all copyright, patent, trademark, and attribution notices from the Source form
98 | of the Work, excluding those notices that do not pertain to any part of the
99 | Derivative Works; and
100 | * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
101 | Derivative Works that You distribute must include a readable copy of the
102 | attribution notices contained within such NOTICE file, excluding those notices
103 | that do not pertain to any part of the Derivative Works, in at least one of the
104 | following places: within a NOTICE text file distributed as part of the
105 | Derivative Works; within the Source form or documentation, if provided along
106 | with the Derivative Works; or, within a display generated by the Derivative
107 | Works, if and wherever such third-party notices normally appear. The contents of
108 | the NOTICE file are for informational purposes only and do not modify the
109 | License. You may add Your own attribution notices within Derivative Works that
110 | You distribute, alongside or as an addendum to the NOTICE text from the Work,
111 | provided that such additional attribution notices cannot be construed as
112 | modifying the License.
113 |
114 | You may add Your own copyright statement to Your modifications and may provide
115 | additional or different license terms and conditions for use, reproduction, or
116 | distribution of Your modifications, or for any such Derivative Works as a whole,
117 | provided Your use, reproduction, and distribution of the Work otherwise complies
118 | with the conditions stated in this License.
119 |
120 | #### 5. Submission of Contributions
121 |
122 | Unless You explicitly state otherwise, any Contribution intentionally submitted
123 | for inclusion in the Work by You to the Licensor shall be under the terms and
124 | conditions of this License, without any additional terms or conditions.
125 | Notwithstanding the above, nothing herein shall supersede or modify the terms of
126 | any separate license agreement you may have executed with Licensor regarding
127 | such Contributions.
128 |
129 | #### 6. Trademarks
130 |
131 | This License does not grant permission to use the trade names, trademarks,
132 | service marks, or product names of the Licensor, except as required for
133 | reasonable and customary use in describing the origin of the Work and
134 | reproducing the content of the NOTICE file.
135 |
136 | #### 7. Disclaimer of Warranty
137 |
138 | Unless required by applicable law or agreed to in writing, Licensor provides the
139 | Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
140 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
141 | including, without limitation, any warranties or conditions of TITLE,
142 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
143 | solely responsible for determining the appropriateness of using or
144 | redistributing the Work and assume any risks associated with Your exercise of
145 | permissions under this License.
146 |
147 | #### 8. Limitation of Liability
148 |
149 | In no event and under no legal theory, whether in tort (including negligence),
150 | contract, or otherwise, unless required by applicable law (such as deliberate
151 | and grossly negligent acts) or agreed to in writing, shall any Contributor be
152 | liable to You for damages, including any direct, indirect, special, incidental,
153 | or consequential damages of any character arising as a result of this License or
154 | out of the use or inability to use the Work (including but not limited to
155 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or
156 | any and all other commercial damages or losses), even if such Contributor has
157 | been advised of the possibility of such damages.
158 |
159 | #### 9. Accepting Warranty or Additional Liability
160 |
161 | While redistributing the Work or Derivative Works thereof, You may choose to
162 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or
163 | other liability obligations and/or rights consistent with this License. However,
164 | in accepting such obligations, You may act only on Your own behalf and on Your
165 | sole responsibility, not on behalf of any other Contributor, and only if You
166 | agree to indemnify, defend, and hold each Contributor harmless for any liability
167 | incurred by, or claims asserted against, such Contributor by reason of your
168 | accepting any such warranty or additional liability.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MysterySoup
2 |
3 | **A collection of pseudo-random algorithms for Elixir.**
4 |
--------------------------------------------------------------------------------
/lib/pcg32.ex:
--------------------------------------------------------------------------------
1 | defmodule MysterySoup.PCG32 do
2 | @moduledoc """
3 | Documentation for `MysterySoup`'s PCG32 implementation.
4 | """
5 |
6 | @type t :: __MODULE__
7 |
8 | defstruct [:state, :inc]
9 |
10 | alias MysterySoup.PCG32.Nif
11 |
12 | @doc """
13 | Initializes a new PCG32 state, seeding with system random.
14 | """
15 | @spec init() :: MysterySoup.t()
16 | def init, do: Nif.init_state()
17 |
18 | @doc """
19 | Generates the next random unsigned 32-bit integer from the PCG32 state.
20 | """
21 | @spec next(MysterySoup.PCG32.t()) :: {integer(), MysterySoup.PCG32.t()}
22 | def next(pcg), do: Nif.next(pcg)
23 |
24 | @doc """
25 | Generates a float between 0 and 1. Consumes multiple `next/1` calls.
26 |
27 | # Notes
28 | This function will pull an indeterminate amount
29 | """
30 | @spec decimal(MysterySoup.PCG32.t()) :: {float(), MysterySoup.PCG32.t()}
31 | def decimal(pcg), do: Nif.next_float(pcg)
32 |
33 | @doc """
34 | Generates an integer between 1 and `sides`, as though rolling a die with as many sides.
35 | """
36 | @spec roll_die(MysterySoup.PCG32.t(), integer()) :: {integer(), MysterySoup.PCG32.t()}
37 | def roll_die(pcg, sides) do
38 | {next, pcg} = next(pcg)
39 |
40 | # Using the remainder will confine the value to a range between `sides - 1` and 0. This is off
41 | # by one, so we simply add 1 to it.
42 | val = rem(next, sides) + 1
43 |
44 | {val, pcg}
45 | end
46 |
47 | @doc """
48 | Generates an integer between `low` and `high`, inclusive.
49 | """
50 | @spec from_range(MysterySoup.PCG32.t(), integer(), integer()) :: {integer(), MysterySoup.PCG32.t()}
51 | def from_range(pcg, low, high) when high > low do
52 | {next, pcg} = next(pcg)
53 |
54 | # Similar to `roll_die/2`, this uses remainders to get a value from the range, then adjusts for
55 | # the offsets
56 | val = rem(next, (high - low)) + low + 1
57 |
58 | {val, pcg}
59 | end
60 |
61 | @doc """
62 | Picks `n` options from `set`.
63 | """
64 | @spec pick_n(MysterySoup.PCG32.t(), integer(), [any()]) :: [any()]
65 | def pick_n(_, n, set) when n == 0 or set == [], do: []
66 | def pick_n(pcg, n, set) when is_list(set) do
67 | gen_pick_n_loop(pcg, {false, n, set}, [])
68 | end
69 |
70 | @doc """
71 | Picks `n` _unique_ options from `set`.
72 | """
73 | @spec pick_n_uniq(MysterySoup.PCG32.t(), integer(), [any()]) :: [any()]
74 | def pick_n_uniq(_, n, set) when n == 0 or set == [], do: []
75 | def pick_n_uniq(pcg, n, set) when is_list(set) do
76 | gen_pick_n_loop(pcg, {false, n, set}, [])
77 | end
78 |
79 | # Common logic for pick of set functions
80 | defp gen_pick_n_loop(pcg, {remove, n, set}, out) when n != 0 do
81 | # Next random
82 | {next, pcg} = next(pcg)
83 |
84 | # The remainder of the random number divided by
85 | # the length produces a valid index.
86 | index = rem(next, Enum.count(set))
87 |
88 | # Add the element to the out list
89 | out = [out | Enum.at(set, index)]
90 |
91 | # If we're removing used values, remove it
92 | set = pop_if(remove, set, index)
93 |
94 | gen_pick_n_loop(pcg, {remove, n - 1, set}, out)
95 | end
96 |
97 | defp gen_pick_n_loop(pcg, {_, 0, _}, out), do: {pcg, out}
98 |
99 | defp pop_if(true, set, i), do: List.delete_at(set, i)
100 | defp pop_if(false, set, _), do: set
101 | end
102 |
--------------------------------------------------------------------------------
/lib/pcg32_nif.ex:
--------------------------------------------------------------------------------
1 | defmodule MysterySoup.PCG32.Nif do
2 | use Rustler, otp_app: :mystery_soup
3 |
4 | def init_state, do: :erlang.nif_error(:nif_not_loaded)
5 |
6 | def next(state), do: :erlang.nif_error(:nif_not_loaded)
7 |
8 | def next_float(state), do: :erlang.nif_error(:nif_not_loaded)
9 | end
10 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule MysterySoup.MixProject do
2 | use Mix.Project
3 |
4 | def project do
5 | [
6 | app: :mystery_soup,
7 | version: "0.1.0",
8 | elixir: "~> 1.10",
9 | start_permanent: Mix.env() == :prod,
10 | compilers: [:rustler] ++ Mix.compilers(),
11 | rustler_crates: [mystery_soup: []],
12 | deps: deps(),
13 | ]
14 | end
15 |
16 | # Run "mix help compile.app" to learn about applications.
17 | def application do
18 | [
19 | extra_applications: [:logger]
20 | ]
21 | end
22 |
23 | # Run "mix help deps" to learn about dependencies.
24 | defp deps do
25 | [
26 | {:rustler, "~> 0.22.0"}
27 | ]
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{
2 | "rustler": {:hex, :rustler, "0.22.0", "e2930f9d6933e910f87526bb0a7f904e32b62a7e838a3ca4a884ee7fdfb957ed", [:mix], [{:toml, "~> 0.5.2", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "01f5989dd511ebec09be481e07d3c59773d5373c5061e09d3ebc3ef61811b49d"},
3 | "toml": {:hex, :toml, "0.5.2", "e471388a8726d1ce51a6b32f864b8228a1eb8edc907a0edf2bb50eab9321b526", [:mix], [], "hexpm", "f1e3dabef71fb510d015fad18c0e05e7c57281001141504c6b69d94e99750a07"},
4 | }
5 |
--------------------------------------------------------------------------------
/native/mystery_soup/.cargo/config:
--------------------------------------------------------------------------------
1 | [target.x86_64-apple-darwin]
2 | rustflags = [
3 | "-C", "link-arg=-undefined",
4 | "-C", "link-arg=dynamic_lookup",
5 | ]
6 |
--------------------------------------------------------------------------------
/native/mystery_soup/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "mystery_soup"
3 | version = "0.1.0"
4 | authors = []
5 | edition = "2018"
6 |
7 | [lib]
8 | name = "mystery_soup"
9 | path = "src/lib.rs"
10 | crate-type = ["cdylib"]
11 |
12 | [dependencies]
13 | bytemuck = "1.2.0"
14 | getrandom = "0.1.14"
15 | randomize = "3.0.1"
16 | rustler = "0.22.0"
17 |
--------------------------------------------------------------------------------
/native/mystery_soup/README.md:
--------------------------------------------------------------------------------
1 | # MysterySoup - Rust
2 |
3 | This code contains the native invocations and base algorithms that back MysterySoup.
4 |
--------------------------------------------------------------------------------
/native/mystery_soup/src/lib.rs:
--------------------------------------------------------------------------------
1 | use randomize::PCG32;
2 | use rustler::{resource, Env, Error, NifResult, NifStruct, Term};
3 |
4 | #[derive(NifStruct, Clone)]
5 | #[module = "MysterySoup.PCG32"]
6 | struct RngState {
7 | state: u64,
8 | inc: u64,
9 | }
10 |
11 | #[rustler::nif]
12 | fn init_state() -> NifResult {
13 | let mut arr = [0_u64, 0];
14 | if getrandom::getrandom(bytemuck::bytes_of_mut(&mut arr)).is_err() {
15 | return Err(Error::Atom("sys_rand_err"));
16 | }
17 | let [state, inc] = arr;
18 | Ok(RngState { state, inc })
19 | }
20 |
21 | #[rustler::nif]
22 | fn next(term_state: RngState) -> (u32, RngState) {
23 | next_prv(term_state)
24 | }
25 |
26 | fn next_prv(term_state: RngState) -> (u32, RngState) {
27 | let RngState { state, inc } = term_state;
28 | let mut rng = PCG32 { state, inc };
29 | (
30 | rng.next_u32(),
31 | RngState {
32 | state: rng.state,
33 | inc: rng.inc,
34 | },
35 | )
36 | }
37 |
38 | #[rustler::nif]
39 | fn next_float(term_state: RngState) -> (f32, RngState) {
40 | let (mut bit_src, mut term_state) = next_prv(term_state);
41 | let mut bit_count = 0;
42 |
43 | let low: f32 = 0.0;
44 | let high: f32 = 1.0;
45 |
46 | let low_exp = (low.to_bits() as i32 >> 23) & 0xff;
47 | let high_exp = (high.to_bits() as i32 >> 23) & 0xff;
48 |
49 | let mut exp = high_exp.wrapping_sub(1);
50 |
51 | while exp > low_exp {
52 | if get_bit(&mut term_state, &mut bit_src, &mut bit_count) == 1 {
53 | break;
54 | }
55 |
56 | exp = exp.wrapping_sub(1);
57 | }
58 |
59 | let (mantissa, mut term_state) = next_prv(term_state);
60 |
61 | let mantissa = mantissa & 0x7fffff;
62 |
63 | if mantissa == 0 && get_bit(&mut term_state, &mut bit_src, &mut bit_count) == 1 {
64 | exp = exp.wrapping_add(1);
65 | }
66 |
67 | let result = ((exp as u32) << 23) | mantissa;
68 |
69 | (f32::from_bits(result), term_state)
70 | }
71 |
72 | fn get_bit(term_state: &mut RngState, bits: &mut u32, i: &mut u32) -> u32 {
73 | if *i == 0 {
74 | let (new_bits, new_state) = next_prv(term_state.clone());
75 |
76 | *bits = new_bits;
77 | *term_state = new_state;
78 | *i = 31;
79 | }
80 |
81 | let bit = *bits & 1;
82 | *bits = *bits >> 1;
83 |
84 | *i -= 1;
85 |
86 | bit
87 | }
88 |
89 | fn load(env: Env<'_>, _: Term<'_>) -> bool {
90 | resource!(RngState, env);
91 | true
92 | }
93 |
94 | rustler::init!(
95 | "Elixir.MysterySoup.PCG32.Nif",
96 | [init_state, next, next_float],
97 | load = load
98 | );
99 |
100 |
--------------------------------------------------------------------------------
/test/mystery_soup_test.exs:
--------------------------------------------------------------------------------
1 | defmodule MysterySoupTest do
2 | use ExUnit.Case
3 | doctest MysterySoup
4 |
5 | test "greets the world" do
6 | assert MysterySoup.hello() == :world
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------