Started working on functions for the UI

This commit is contained in:
furo 2021-11-04 15:40:12 +01:00
parent 5319966d7f
commit 2aaa108521
9 changed files with 530 additions and 69 deletions

View File

@ -1,5 +1,6 @@
import { Router } from 'express' import { Router } from 'express'
import { sqlite } from '../libs/database/init.js' import { sqlite } from '../libs/database/init.js'
import wrapper from '../libs/database/wrapper.js'
const finishApi = Router() const finishApi = Router()
@ -16,4 +17,43 @@ finishApi.get(
} }
) )
finishApi.get(
'/finishedMaps/:player',
(req, res) => {
/* Check if player exists */
if(!wrapper.playerExists(req.params.player)) {
return res.json({
success: false,
response: "No such player!"
})
}
const finishes = wrapper.finishedMaps(req.params.player)
return res.json({
success: true,
response: finishes,
})
}
)
finishApi.get(
'/unfinishedMaps/:player',
(req, res) => {
/* Check if player exists */
if(!wrapper.playerExists(req.params.player)) {
return res.json({
success: false,
response: "No such player!"
})
}
const finishes = wrapper.unfinishedMaps(req.params.player)
return res.json({
success: true,
response: finishes,
})
}
)
export default finishApi export default finishApi

View File

@ -1,5 +1,6 @@
import { Router } from 'express' import { Router } from 'express'
import { sqlite } from '../libs/database/init.js' import { sqlite } from '../libs/database/init.js'
import wrapper, { map } from '../libs/database/wrapper.js'
const mapApi = Router() const mapApi = Router()
@ -18,37 +19,17 @@ mapApi.get(
mapApi.get( mapApi.get(
'/get/:map', '/get/:map',
(req, res) => { (req, res) => {
let map = req.params.map
/* Check if map exists */ /* Check if map exists */
const check = sqlite.prepare(`SELECT map FROM maps WHERE map = ?`).get(map) if(!wrapper.mapExists(req.params.map)) {
if (!check) {
return res.json({ return res.json({
success: false, success: false,
response: "No map found!", response: "No such map!"
}) })
} }
const info = sqlite.prepare(`SELECT * FROM maps WHERE map = ?`).get(map)
/* TODO: Generate a table with this as a cache */
const avgTime = sqlite.prepare(`SELECT avg(time) AS 'averageTime' FROM race WHERE map = ?`).get(map)
const total = sqlite.prepare(`SELECT COUNT(*) AS 'total' FROM race WHERE map = ?`).get(map)
const unique = sqlite.prepare(`SELECT COUNT(distinct(player)) AS 'unique' FROM race WHERE map = ?`).get(map)
const teams = sqlite.prepare(`SELECT COUNT(distinct(id)) AS 'teams' FROM teamrace WHERE map = ?`).get(map)
return res.json({ return res.json({
success: true, success: true,
response: { response: map(req.params.map)
info,
/* TODO Get median time*/
averageTime: avgTime.averageTime,
finishes: {
unique: unique.unique,
total: total.total,
teams: teams.teams,
}
}
}) })
} }
) )
@ -56,11 +37,9 @@ mapApi.get(
mapApi.get( mapApi.get(
'/getAll', '/getAll',
(req, res) => { (req, res) => {
const allMaps = sqlite.prepare(`SELECT * FROM maps`).all()
return res.json({ return res.json({
success: true, success: true,
response: allMaps, response: wrapper.allMaps()
}) })
} }
) )
@ -68,22 +47,53 @@ mapApi.get(
mapApi.get( mapApi.get(
'/category/:category', '/category/:category',
(req, res) => { (req, res) => {
let category = req.params.category
/* Check if category exists */ /* Check if category exists */
const check = sqlite.prepare(`SELECT category FROM maps WHERE category = ? LIMIT 1`).get(category) if (!wrapper.categoryExists(req.params.category)) {
if (!check) {
return res.json({ return res.json({
success: false, success: false,
response: "Invalid category name!", response: "Invalid category name!",
}) })
} }
const allMaps = sqlite.prepare(`SELECT * FROM maps WHERE category = ?`).all(category) return res.json({
success: true,
response: wrapper.mapCategory(req.params.category)
})
}
)
mapApi.get(
'/leaderboard/race/:map',
(req, res) => {
/* Check if map exists */
if (!wrapper.mapExists(req.params.map)) {
return res.json({
success: false,
response: "No such map!",
})
}
return res.json({ return res.json({
success: true, success: true,
response: allMaps, response: wrapper.leaderboardRace(req.params.map, 1, 20)
})
}
)
mapApi.get(
'/leaderboard/teamrace/:map',
(req, res) => {
/* Check if map exists */
if (!wrapper.mapExists(req.params.map)) {
return res.json({
success: false,
response: "No such map!",
})
}
return res.json({
success: true,
response: wrapper.leaderboardTeamrace(req.params.map, 1, 20)
}) })
} }
) )

View File

@ -1,32 +1,24 @@
import { Router } from 'express' import { Router } from 'express'
import { sqlite } from '../libs/database/init.js' import wrapper from '../libs/database/wrapper.js'
import searcher from '../libs/database/searcher.js'
const playerApi = Router() const playerApi = Router()
playerApi.get( playerApi.get(
'/get/:player', '/get/:player',
async (req, res) => { async (req, res) => {
searcher( /* Check if player exists */
'points', if(!wrapper.playerExists(req.params.player)) {
'player', return res.json({
req.params.player,
undefined,
false,
"get",
0
).then(
player => res.json({
success: true,
response: player
})
).catch(
error => res.json({
success: false, success: false,
response: error response: "No such player!"
})
}
const data = wrapper.player(req.params.player)
return res.json({
success: true,
response: data
}) })
)
} }
) )

View File

@ -4,7 +4,7 @@ import api from './api/api.js'
import { generateDB } from './libs/database/generate.js' import { generateDB } from './libs/database/generate.js'
import { dbInit } from './libs/database/init.js' import { dbInit } from './libs/database/init.js'
import { downloadEssentialData } from './libs/download/dowload.js' import { downloadEssentialData } from './libs/download/dowload.js'
import tasks from './libs/database/tasks.js'
loadEnv() loadEnv()

View File

@ -43,11 +43,6 @@ export function generateDB() {
`ALTER TABLE maps RENAME COLUMN Timestamp TO release` `ALTER TABLE maps RENAME COLUMN Timestamp TO release`
]) ])
} }
log("Generating map index...")
execMany([
`CREATE INDEX IF NOT EXISTS "idx_maps_map" ON "maps" ("map")`,
`CREATE INDEX IF NOT EXISTS "idx_maps_category" ON "maps" ("category")`
])
log("Generating race index...") log("Generating race index...")
execMany([ execMany([
@ -62,12 +57,15 @@ export function generateDB() {
log("Creating rankings table...") log("Creating rankings table...")
sqlite.exec(` sqlite.exec(`
CREATE TABLE IF NOT EXISTS "rankings" ( CREATE TABLE IF NOT EXISTS "rankings" (
"category" varchar(32) NOT NULL,
"points" integer NOT NULL DEFAULT 0,
"map" varchar(128) NOT NULL, "map" varchar(128) NOT NULL,
"player" varchar(16) NOT NULL, "player" varchar(16) NOT NULL,
"time" float NOT NULL DEFAULT 0, "time" float NOT NULL DEFAULT 0,
"date" timestamp NOT NULL DEFAULT current_timestamp, "date" timestamp NOT NULL DEFAULT current_timestamp,
"server" char(4) NOT NULL DEFAULT '', "server" char(4) NOT NULL DEFAULT '',
"rank" INTEGER NOT NULL "rank" INTEGER NOT NULL,
"finishes" INTEGER NOT NULL DEFAULT 0
) )
`) `)
@ -78,7 +76,9 @@ export function generateDB() {
execMany([ execMany([
`CREATE INDEX IF NOT EXISTS "idx_rankings_map" ON "rankings" ("map")`, `CREATE INDEX IF NOT EXISTS "idx_rankings_map" ON "rankings" ("map")`,
`CREATE INDEX IF NOT EXISTS "idx_rankings_rank" ON "rankings" ("rank")`, `CREATE INDEX IF NOT EXISTS "idx_rankings_rank" ON "rankings" ("rank")`,
`CREATE INDEX IF NOT EXISTS "idx_rankings_player" ON "rankings" ("player")` `CREATE INDEX IF NOT EXISTS "idx_rankings_player" ON "rankings" ("player")`,
`CREATE INDEX IF NOT EXISTS "idx_rankings_finishes" ON "rankings" ("finishes")`,
`CREATE INDEX IF NOT EXISTS "idx_rankings_mapRank" ON "rankings" ("map", "rank")`
]) ])
log("Generating teamrace index...") log("Generating teamrace index...")
@ -91,6 +91,8 @@ export function generateDB() {
log("Creating teamrankings table...") log("Creating teamrankings table...")
sqlite.exec(` sqlite.exec(`
CREATE TABLE IF NOT EXISTS "teamrankings" ( CREATE TABLE IF NOT EXISTS "teamrankings" (
"category" varchar(32) NOT NULL,
"points" integer NOT NULL DEFAULT 0,
"map" varchar(128) NOT NULL, "map" varchar(128) NOT NULL,
"id" varbinary(16) NOT NULL, "id" varbinary(16) NOT NULL,
"player" varchar(16) NOT NULL, "player" varchar(16) NOT NULL,
@ -108,7 +110,9 @@ export function generateDB() {
execMany([ execMany([
`CREATE INDEX IF NOT EXISTS "idx_teamrankings_map" ON "teamrankings" ("map")`, `CREATE INDEX IF NOT EXISTS "idx_teamrankings_map" ON "teamrankings" ("map")`,
`CREATE INDEX IF NOT EXISTS "idx_teamrankings_rank" ON "teamrankings" ("teamrank")`, `CREATE INDEX IF NOT EXISTS "idx_teamrankings_rank" ON "teamrankings" ("teamrank")`,
`CREATE INDEX IF NOT EXISTS "idx_teamrankings_player" ON "teamrankings" ("player")` `CREATE INDEX IF NOT EXISTS "idx_teamrankings_player" ON "teamrankings" ("player")`,
`CREATE INDEX IF NOT EXISTS "idx_teamrankings_playerCategoryMap" ON "teamrankings" ("player", "category", "map")`,
`CREATE INDEX IF NOT EXISTS "idx_teamrankings_mapTeamrank" ON "teamrankings" ("map", "teamrank")`
]) ])
log("Generating graphRecordCache...") log("Generating graphRecordCache...")
@ -131,6 +135,38 @@ export function generateDB() {
log("Inserting points to DB...") log("Inserting points to DB...")
tasks.processAllPoints() tasks.processAllPoints()
log("Generating a new maps table...")
/* Rename the old one as we wanna use that name for the new one*/
sqlite.exec(`ALTER TABLE maps RENAME TO oldmaps`)
sqlite.exec(`
CREATE TABLE IF NOT EXISTS "maps" (
"map" varchar(128) NOT NULL,
"category" varchar(32) NOT NULL,
"points" integer NOT NULL DEFAULT 0,
"stars" integer NOT NULL DEFAULT 0,
"mapper" char(128) NOT NULL,
"release" timestamp NOT NULL DEFAULT current_timestamp,
"avgTime" FLOAT NOT NULL DEFAULT 0,
"medianTime" FLOAT NOT NULL DEFAULT 0,
"topTime" FLOAT NOT NULL DEFAULT 0,
"topTeamTime" FLOAT NOT NULL DEFAULT 0,
"finishesUnique" INTEGER NOT NULL DEFAULT 0,
"finishesTotal" INTEGER NOT NULL DEFAULT 0,
"finishesTeam" INTEGER NOT NULL DEFAULT 0
)
`)
tasks.processMaps()
log("Generating map index...")
execMany([
`CREATE INDEX IF NOT EXISTS "idx_maps_map" ON "maps" ("map")`,
`CREATE INDEX IF NOT EXISTS "idx_maps_category" ON "maps" ("category")`
`CREATE INDEX IF NOT EXISTS "idx_maps_categoryMap" ON "maps" ("category", "map")`
])
skinDB.exec(` skinDB.exec(`
CREATE TABLE IF NOT EXISTS "skindata" ( CREATE TABLE IF NOT EXISTS "skindata" (
"timestamp" INTEGER NOT NULL, "timestamp" INTEGER NOT NULL,

View File

@ -24,6 +24,9 @@ export function dbInit() {
/* Unsafe mode */ /* Unsafe mode */
sqlite.unsafeMode() sqlite.unsafeMode()
/* Load external extensions */
sqlite.loadExtension('./math-func.so')
log("Loaded in 'ddnet.sqlite'!") log("Loaded in 'ddnet.sqlite'!")
log("Loaded in 'skindata.sqlite'!") log("Loaded in 'skindata.sqlite'!")
} }

View File

@ -7,26 +7,28 @@ import { sqlite } from './init.js'
* @module libs/database/processRankings * @module libs/database/processRankings
*/ */
export function processRankings() { export function processRankings() {
const maps = sqlite.prepare(`SELECT map FROM maps`) const maps = sqlite.prepare(`SELECT * FROM maps`)
for (const map of maps.iterate()) for (const map of maps.iterate())
sqlite sqlite
.prepare(` .prepare(`
INSERT INTO rankings INSERT INTO rankings
( (
map, player, time, date, rank, server map, category, points, player, time, date, rank, server, finishes
) )
SELECT map, player, time, date, rank, server SELECT a.map, b.category, b.points, a.player, a.time, a.date, a.rank, a.server, a.finishes
FROM ( FROM (
SELECT rank() OVER w AS rank, SELECT rank() OVER w AS rank,
map, map,
date, date,
player, player,
min(time) AS time, min(time) AS time,
server server,
FROM race COUNT(*) AS finishes
FROM race as a
WHERE map = ? WHERE map = ?
GROUP BY player window w AS (ORDER BY time) ) AS a GROUP BY player window w AS (ORDER BY time) ) AS a
JOIN maps as b ON a.map = b.map
ORDER BY rank ORDER BY rank
`) `)
.run(map.map) .run(map.map)
@ -44,12 +46,13 @@ export function processTeamRankings() {
.prepare(` .prepare(`
INSERT INTO teamrankings INSERT INTO teamrankings
( (
player, map, id, time, date, server, teamrank player, map, id, time, date, server, teamrank, category, points
) )
SELECT DISTINCT(r.player), SELECT DISTINCT(r.player),
r.map, r.id, r.time, r.date, r.map, r.id, r.time, r.date,
Substring(n.server, 1, 3), Substring(n.server, 1, 3),
dense_rank() OVER w AS rank dense_rank() OVER w AS rank,
a.category, a.points
FROM (( FROM ((
SELECT DISTINCT id SELECT DISTINCT id
FROM teamrace FROM teamrace
@ -61,7 +64,9 @@ export function processTeamRankings() {
INNER JOIN race AS n INNER JOIN race AS n
ON r.map = n.map ON r.map = n.map
AND r.player = n.player AND r.player = n.player
AND r.time = n.time window w AS (ORDER BY r.time) AND r.time = n.time
JOIN maps as a
ON r.map = a.map window w AS (ORDER BY r.time)
`) `)
.run(map.map) .run(map.map)
} }
@ -99,6 +104,45 @@ export function processTimeGraph() {
} }
} }
/**
* This generates a fancy map table containing more data such total finishes, median time.
* @module libs/database/processMaps
*/
export function processMaps() {
const maps = sqlite.prepare(`SELECT map FROM oldmaps`)
const finishes = sqlite.prepare(`SELECT * FROM race WHERE map = ? ORDER BY date`)
for (const map of maps.iterate()) {
const info = sqlite.prepare(`SELECT * FROM oldmaps WHERE map = ?`).get(map.map)
const avgTime = sqlite.prepare(`SELECT avg(time) AS avgTime FROM race WHERE map = ?`).get(map.map)?.avgTime ?? -1
const medianTime = sqlite.prepare(`SELECT median(time) as medianTime FROM race WHERE map = ?`).get(map.map)?.medianTime ?? -1
const teams = sqlite.prepare(`SELECT COUNT(distinct(id)) AS 'teams' FROM teamrace WHERE map = ?`).get(map.map)?.teams ?? -1
const topTeamTime = sqlite.prepare(`SELECT time as topTeamTime FROM teamrankings WHERE map = ? ORDER BY Time ASC LIMIT 1`).get(map.map)?.topTeamTime ?? -1
const topTime = sqlite.prepare(`SELECT time as topTime FROM rankings WHERE map = ? ORDER BY Time ASC LIMIT 1`).get(map.map)?.topTime ?? -1
const total = sqlite.prepare(`SELECT COUNT(*) AS 'total' FROM race WHERE map = ?`).get(map.map)?.total ?? -1
const unique = sqlite.prepare(`SELECT COUNT(distinct(player)) AS 'unique' FROM race WHERE map = ?`).get(map.map)?.unique ?? -1
sqlite.prepare(`
INSERT INTO "maps"
(
map, category, points, stars, mapper, release,
avgTime, medianTime, topTime, topTeamTime,
finishesUnique, finishesTotal, finishesTeam
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(
info.map, info.category, info.points, info.stars, info.mapper, info.release,
avgTime, medianTime, topTime, topTeamTime,
unique, total, teams,
)
}
}
/** /**
* This inserts all types of points into a table... * This inserts all types of points into a table...
* @module db/processAllPoints * @module db/processAllPoints
@ -160,5 +204,6 @@ export default {
processAllPoints, processAllPoints,
processRankings, processRankings,
processTeamRankings, processTeamRankings,
processMaps,
processTimeGraph processTimeGraph
} }

335
libs/database/wrapper.js Normal file
View File

@ -0,0 +1,335 @@
import { sqlite } from './init.js'
/**
* This function checks if a player exists
*
* @param {string} player The player to check
* @returns {boolean} Returns a boolean
*/
export function playerExists(player) {
const exists = sqlite.prepare(`SELECT * FROM points WHERE player = ? LIMIT 1`).get(player)
if(exists)
return true
else
return false
}
/**
* This function checks if a map exists
*
* @param {string} player The map to check
* @returns {boolean} Returns a boolean
*/
export function mapExists(map) {
const exists = sqlite.prepare(`SELECT * FROM maps WHERE map = ? LIMIT 1`).get(map)
if(exists)
return true
else
return false
}
/**
* This function checks if a map category exists
*
* @param {string} player The category to check
* @returns {boolean} Returns a boolean
*/
export function categoryExists(category) {
const exists = sqlite.prepare(`SELECT category FROM maps WHERE category = ? LIMIT 1`).get(category)
if(exists)
return true
else
return false
}
/**
* This function returns all data pertaining a certain player
*
* @param {string} player The player to fetch
* @returns {object} An object containing the players data
*/
export function player(player) {
/* Misc */
const firstFinish = sqlite.prepare(`SELECT map, time, date, server FROM race WHERE player = ? ORDER BY date ASC LIMIT 1`).get(player)
/* Points */
let points = {}
let rank = {}
const pointsData = sqlite.prepare(`SELECT * FROM points WHERE player = ?`)
for (const pointsType of pointsData.iterate(player)) {
rank[pointsType.type] = pointsType.rank
}
for (const pointsType of pointsData.iterate(player)) {
points[pointsType.type] = pointsType.points
}
return {
player,
firstFinish,
points,
rank,
}
}
/**
* This function returns all data pertaining a certain map
*
* @param {string} map The map to fetch
* @returns {object} An object containing map data
*/
export function map(map) {
const a = sqlite.prepare(`
SELECT * FROM maps WHERE map = ?
`).get(map)
return prettyifyMap(a)
}
export function mapCategory(category) {
let output = []
const maps = sqlite.prepare(`
SELECT * FROM maps WHERE category = ?`).all(category)
for(const map of maps) {
output.push(prettyifyMap(map))
}
return output
}
/**
* This function returns all data pertaining to all maps
* @returns {array} An array contaning all map objects
*/
export function allMaps() {
let output = []
const maps = sqlite.prepare(`
SELECT * FROM maps`).all()
for(const map of maps) {
output.push(prettyifyMap(map))
}
return output
}
/**
* This function returns all data pertaining a certain map
*
* @param {string} map The map to fetch
* @returns {object} An object containing map data
*/
export function prettyifyMap(a) {
let output
output = {
map: a.map,
category: a.category,
points: a.points,
stars: a.stars,
release: a.release,
mappers: a.mapper.split(" & "),
times: {
average: a.avgTime,
median: a.medianTime,
topTime: a.topTime,
topTimeTeam: (a.topTeamTime != -1) ? a.topTeamTime : undefined,
},
finishes: {
total: a.finishesTotal,
team: a.finishesTeam,
unique: a.finishesUnique,
}
}
return output
}
/**
* This function returns the race leaderboard for a map
*
* @param {string} map The map to check
* @param {number} start At which rank the leaderboard should begin
* @param {number} end At which rank the leaderboard should end
* @returns {array} An array containing the leaderboard
*/
export function leaderboardRace(map, start, end) {
const leaderboard = sqlite.prepare(`
SELECT rank, time, date, player, server FROM rankings WHERE map = ? AND rank >= ? AND rank <= ?`)
.all(map, start, end)
return leaderboard
}
/**
* This function returns the teamrace leaderboard for a map
*
* @param {string} map The map to check
* @param {number} start At which rank the leaderboard should begin
* @param {number} end At which rank the leaderboard should end
* @returns {array} An array containing the leaderboard
*/
export function leaderboardTeamrace(map, start, end) {
// TODO: Optimize array creation of players
let leaderboard = []
const a = sqlite.prepare(`
SELECT teamrank, time, date, player, server FROM teamrankings WHERE map = ? AND teamrank >= ? AND teamrank <= ? GROUP BY teamrank`)
for(const teamrank of a.iterate(map, start, end)) {
let players = []
const b = sqlite.prepare(`SELECT player FROM teamrankings WHERE map = ? AND teamrank = ?`)
for(const player of b.iterate(map, teamrank.teamrank)) {
players.push(player.player)
}
leaderboard.push({
teamrank: teamrank.teamrank,
time: teamrank.time,
date: teamrank.date,
server: teamrank.server,
players: players,
})
}
return leaderboard
}
/**
* This function returns the points leaderboard for a specific type
* (points, pointsRank, pointsTeam, pointsThisWeek, pointsThisMonth)
*
* @param {string} type Which type of points to fetch
* @param {number} start At which rank the leaderboard should begin
* @param {number} end At which rank the leaderboard should end
* @returns {array} An array containing the leaderboard
*/
export function leaderboardPoints(map, start, end) {
const leaderboard = sqlite.prepare(`
SELECT rank, player, points FROM points WHERE type = ? AND rank >= ? AND rank <= ? ORDER BY rank`)
.all(type, start, end)
return leaderboard
}
/**
* This function returns all finished maps by a specific player
* togheter with their respective rank, teamrank, amount of finishes.
* Finishes are grouped by map category (Novice, Brutal)
*
* @param {string} player The player to check
* @returns {object} An object containing all finishes grouped by category
*/
export function finishedMaps(player) {
const finishesStmt = sqlite.prepare(
`
SELECT a.map,
a.category,
a.points,
a.rank,
b.teamrank,
a.finishes
FROM rankings AS a
LEFT OUTER JOIN teamrankings AS b
ON a.player = b.player
AND a.category = b.category
AND a.map = b.map
WHERE a.player = ?
`)
let finishes = {
Novice: [],
Moderate: [],
Brutal: [],
Insane: [],
Dummy: [],
DDmaX: [],
Oldschool: [],
Solo: [],
Race: [],
Fun: []
}
for (const finish of finishesStmt.iterate(player)) {
finishes[finish.category].push(finish)
}
return finishes
}
/**
* This function returns all unfinished maps by a specific player
* togheter with category, points, finishTotal and medianTime.
* Maps are grouped by the map category (Novice, Brutal)
*
* @param {string} player The player to check
* @returns {object} An object containing all unfinished maps
*/
export function unfinishedMaps(player) {
const maps = sqlite.prepare(
`
SELECT a.map,
a.category,
a.points,
b.finishesTotal,
b.medianTime
FROM (SELECT category,
map,
points
FROM maps
WHERE map NOT IN (SELECT map
FROM rankings
WHERE player = ? )) AS a
JOIN maps AS b
ON a.category = b.category
AND a.map = b.map
ORDER BY b.category ASC;
`)
let unfinished = {
Novice: [],
Moderate: [],
Brutal: [],
Insane: [],
Dummy: [],
DDmaX: [],
Oldschool: [],
Solo: [],
Race: [],
Fun: []
}
for (const map of maps.iterate(player)) {
unfinished[map.category].push(map)
}
return unfinished
}
export default {
playerExists,
finishedMaps,
unfinishedMaps,
player,
map,
mapCategory,
allMaps,
mapExists,
leaderboardRace,
leaderboardTeamrace,
categoryExists,
}

BIN
math-func.so Executable file

Binary file not shown.