Initial commit
parent
3d0a75dbf1
commit
7410ac443f
|
@ -0,0 +1 @@
|
|||
default
|
|
@ -0,0 +1,89 @@
|
|||
-- Catapults mod by xyz
|
||||
|
||||
-- How many seconds required to push one cannon ball
|
||||
local catapult_speed = 5
|
||||
|
||||
minetest.register_node("catapults:catapult", {
|
||||
tile_inages = {"catapults_catapult.png"},
|
||||
inventory_image = minetest.inventorycube("catapults_catapult.png"),
|
||||
material = minetest.digprop_woodlike(1.5),
|
||||
param = "facedir_simple",
|
||||
metadata_name = "generic"
|
||||
})
|
||||
|
||||
minetest.register_craftitem("catapults:cannon_ball", {
|
||||
image = "catapults_cannon_ball.png"
|
||||
})
|
||||
|
||||
--[[
|
||||
minetest.register_craft(
|
||||
output = 'craft "catapults:cannon_ball" 1',
|
||||
recipe = {
|
||||
|
||||
}
|
||||
)
|
||||
]]
|
||||
|
||||
minetest.register_entity("catapults:cannon_ball_flying", {
|
||||
physical = true,
|
||||
collisionbox = {-0.2, -0.2, -0.2, 0.2, 0.2, 0.2},
|
||||
visual = "sprite",
|
||||
textures = {"catapults_cannon_ball.png"},
|
||||
on_step = function(self, dtime)
|
||||
local pos = self.object:getpos()
|
||||
local bcp = {x=pos.x, y=pos.y-0.7, z=pos.z}
|
||||
local bcn = minetest.env:get_node(bcp)
|
||||
if bcn.name ~= "air" then
|
||||
-- TODO: destroy something
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
-- Returns timestamp
|
||||
local function get_time()
|
||||
return os.time()
|
||||
end
|
||||
|
||||
minetest.register_on_punchnode(function(pos, node, puncher)
|
||||
if node.name == "catapults:catapult" then
|
||||
local catapult_meta = minetest.env:get_meta(pos)
|
||||
local time_to_push = catapult_meta:get_string('time_to_push')
|
||||
if time_to_push == nil or time_to_push == '' then
|
||||
-- putin' some ball
|
||||
print('putin')
|
||||
catapult_meta:set_string('time_to_push', catapult_speed + get_time())
|
||||
elseif tonumber(time_to_push) < get_time() then
|
||||
print('Pushing')
|
||||
local ball = minetest.env:add_entity({x = pos.x, y = pos.y + 1, z = pos.z}, "catapults:cannon_ball_flying")
|
||||
ball:setacceleration({x = 0, y = -10, z = 0})
|
||||
-- TODO: velocity should depend on catapult rotation
|
||||
print(node.param2)
|
||||
ball:setvelocity({x = 50, y = 30, z = 0})
|
||||
catapult_meta:set_string('time_to_push', '')
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_abm({
|
||||
nodenames = "catapults:catapult",
|
||||
interval = 1.0,
|
||||
chance = 1,
|
||||
action = function(pos, node)
|
||||
-- set loaded status
|
||||
local catapult_meta = minetest.env:get_meta(pos)
|
||||
local time_to_push = catapult_meta:get_string('time_to_push')
|
||||
print(get_time())
|
||||
print(time_to_push)
|
||||
local s = ''
|
||||
local time = get_time()
|
||||
if time_to_push == nil or time_to_push == "" then
|
||||
s = 'Click to load ball'
|
||||
elseif tonumber(time_to_push) <= time then
|
||||
s = 'Ready to shot!'
|
||||
else
|
||||
s = math.floor((1 - (tonumber(time_to_push) - time) / catapult_speed) * 100).."%"
|
||||
end
|
||||
catapult_meta:set_infotext(s)
|
||||
end
|
||||
})
|
|
@ -0,0 +1 @@
|
|||
default
|
|
@ -0,0 +1,413 @@
|
|||
-- Seasons mod by xyz
|
||||
|
||||
-- Some constant values
|
||||
-- Feel free to modify them
|
||||
|
||||
-- Time amount, after what ABM '' table for winter will be built
|
||||
-- This is required because Minetest doesn't have function that run after all nodes were registered
|
||||
local time_to_load = 5
|
||||
|
||||
-- How many seconds one season lasts?
|
||||
local season_duration = 600
|
||||
---------------------------
|
||||
|
||||
-- Profiler stuff
|
||||
dofile(minetest.get_modpath('seasons')..'/profiler.lua')
|
||||
local profiler = newProfiler()
|
||||
profiler:start()
|
||||
local function stopProfiler()
|
||||
profiler:stop()
|
||||
local outfile = io.open("profile.txt", "w+")
|
||||
profiler:report(outfile)
|
||||
outfile:close()
|
||||
end
|
||||
minetest.register_on_chat_message(function(name, message)
|
||||
if message == "/stop" then
|
||||
stopProfiler()
|
||||
minetest.chat_send_player(name, "Profiler stopped!")
|
||||
end
|
||||
end)
|
||||
----------------
|
||||
|
||||
math.randomseed(os.time())
|
||||
|
||||
local season_time = 0.0
|
||||
local time_file = minetest.get_modpath('seasons')..'/'..'time'
|
||||
-- init seasons
|
||||
local f = io.open(time_file, "r")
|
||||
season_time = f:read("*n")
|
||||
io.close(f)
|
||||
|
||||
local function pp(x, y, z)
|
||||
return "("..x.." "..y.." "..z..")"
|
||||
end
|
||||
|
||||
local function get_season_time()
|
||||
return season_time
|
||||
end
|
||||
|
||||
local function set_season_time(t)
|
||||
season_time = t
|
||||
-- write to file
|
||||
local f = io.open(time_file, "w")
|
||||
f:write(season_time)
|
||||
io.close(f)
|
||||
end
|
||||
|
||||
local cur_season = ""
|
||||
|
||||
-- spring, summer, autumn, winter
|
||||
--[[function cur_season
|
||||
if season_time < 1 then
|
||||
return "spring"
|
||||
elseif season_time < 2 then
|
||||
return "summer"
|
||||
elseif season_time < 3 then
|
||||
return "autumn"
|
||||
else
|
||||
return "winter"
|
||||
end
|
||||
end]]
|
||||
|
||||
minetest.register_node("seasons:treehead", {
|
||||
tile_images = {"default_tree_top.png", "default_tree_top.png", "default_tree.png"},
|
||||
inventory_image = minetest.inventorycube("default_tree_top.png", "default_tree.png", "default_tree.png"),
|
||||
is_ground_content = true,
|
||||
material = minetest.digprop_woodlike(1.0),
|
||||
dug_item = 'node "tree" 1',
|
||||
})
|
||||
|
||||
minetest.register_node("seasons:ice", {
|
||||
tile_images = {"seasons_ice.png"},
|
||||
inventory_image = minetest.inventorycube("seasons_ice.png"),
|
||||
is_ground_content = true,
|
||||
material = minetest.digprop_woodlike(1.0),
|
||||
dug_item = '',
|
||||
})
|
||||
|
||||
minetest.register_node("seasons:autumn_leaves", {
|
||||
drawtype = "allfaces_optional",
|
||||
visual_scale = 1.3,
|
||||
tile_images = {"seasons_autumn_leaves.png"},
|
||||
inventory_image = minetest.inventorycube("seasons_autumn_leaves.png"),
|
||||
paramtype = "light",
|
||||
material = minetest.digprop_leaveslike(0.5),
|
||||
dug_item = ''
|
||||
})
|
||||
|
||||
minetest.register_node("seasons:autumn_falling_leaves", {
|
||||
drawtype = "allfaces_optional",
|
||||
visual_scale = 1.3,
|
||||
tile_images = {"seasons_autumn_leaves.png"},
|
||||
inventory_image = minetest.inventorycube("seasons_autumn_leaves.png"),
|
||||
paramtype = "light",
|
||||
material = minetest.digprop_leaveslike(0.5),
|
||||
dug_item = ''
|
||||
})
|
||||
|
||||
default.register_falling_node("seasons:autumn_falling_leaves", "seasons_autumn_leaves.png")
|
||||
|
||||
minetest.register_node("seasons:snow", {
|
||||
drawtype = "signlike",
|
||||
tile_images = {"seasons_snow.png"},
|
||||
inventory_image = "seasons_snow.png",
|
||||
paramtype = "light",
|
||||
is_ground_content = true,
|
||||
wall_mounted = true,
|
||||
walkable = false,
|
||||
selection_box = {
|
||||
type = "wallmounted",
|
||||
},
|
||||
material = minetest.digprop_dirtlike(0.4),
|
||||
dug_item = ''
|
||||
})
|
||||
|
||||
local function vector_length(v)
|
||||
return math.sqrt(v.x*v.x + v.y*v.y + v.z*v.z)
|
||||
end
|
||||
|
||||
local function vector_resize(v, l)
|
||||
local s = vector_length(v)
|
||||
nv = {x = 0.0, y = 0.0, z = 0.0}
|
||||
nv.x = v.x / s * l
|
||||
nv.y = v.y / s * l
|
||||
nv.z = v.z / s * l
|
||||
return nv
|
||||
end
|
||||
|
||||
minetest.register_craftitem("seasons:snowball", {
|
||||
image = "seasons_snowball.png",
|
||||
on_drop = function(item, dropper, pos)
|
||||
local p = dropper:getpos()
|
||||
p.y = p.y + 1
|
||||
local x = minetest.env:add_entity(p, "seasons:snowball_flying")
|
||||
x:setacceleration({x = 0, y = -10, z = 0})
|
||||
local look_dir = dropper:get_look_dir()
|
||||
print(pp(look_dir.x, look_dir.y, look_dir.z))
|
||||
-- TODO: resize look_dir
|
||||
x:setvelocity(vector_resize(look_dir, 10))
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_entity("seasons:snowball_flying", {
|
||||
physical = true,
|
||||
collisionbox = {-0.3, -0.3, -0.3, 0.3, 0.3, 0.3},
|
||||
visual = "sprite",
|
||||
textures = {"seasons_snowball.png"},
|
||||
on_step = function(self, dtime)
|
||||
local pos = self.object:getpos()
|
||||
local bcp = {x=pos.x, y=pos.y-0.7, z=pos.z}
|
||||
local bcn = minetest.env:get_node(bcp)
|
||||
if bcn.name ~= "air" then
|
||||
self.object:remove()
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_on_generated(function(minp, maxp)
|
||||
-- replace top tree block with TREEHEAD
|
||||
-- TODO: it should definetly be done in sources
|
||||
for x = minp.x, maxp.x do
|
||||
for z = minp.z, maxp.z do
|
||||
for ly = minp.y, maxp.y do
|
||||
-- TODO: fix that
|
||||
local y = maxp.y + minp.y - ly
|
||||
if minetest.env:get_node({x = x, y = y, z = z}).name == "default:tree" then
|
||||
--print("New treenode at "..pp(x, y, z))
|
||||
minetest.env:add_node({x = x, y = y, z = z}, {name = "seasons:treehead"})
|
||||
local ny = y - 1
|
||||
local t_node = minetest.env:get_node({x = x, y = ny, z = z})
|
||||
while t_node.name == "default:tree" or t_node.name == "seasons:treehead" do
|
||||
-- if there is already treehead below me, it should be removed
|
||||
if t_node.name == "seasons:treehead" then
|
||||
minetest.env:add_node({x = x, y = ny, z = z}, {name = "tree"})
|
||||
--print("Old treehead removed at "..pp(x, y, z))
|
||||
end
|
||||
ny = ny - 1
|
||||
t_node = minetest.env:get_node({x = x, y = ny, z = z})
|
||||
end
|
||||
break
|
||||
else
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local delta = 0.0
|
||||
minetest.register_globalstep(function(dtime)
|
||||
delta = delta + dtime
|
||||
if delta > 5 then
|
||||
local time = get_season_time() + delta / season_duration
|
||||
set_season_time(time)
|
||||
if time >= 4 then
|
||||
set_season_time(time - 4)
|
||||
time = time - 4
|
||||
end
|
||||
if time < 1 then
|
||||
cur_season = "spring"
|
||||
elseif time < 2 then
|
||||
cur_season = "summer"
|
||||
elseif time < 3 then
|
||||
cur_season = "autumn"
|
||||
else
|
||||
cur_season = "winter"
|
||||
end
|
||||
print(cur_season.." "..time)
|
||||
delta = 0
|
||||
end
|
||||
end)
|
||||
|
||||
-- leaves become orange in autumn
|
||||
minetest.register_abm({
|
||||
nodenames = {"default:leaves"},
|
||||
neighbors = {"air", "seasons:autumn_leaves"},
|
||||
interval = 5.0,
|
||||
chance = 10,
|
||||
action = function(pos, node)
|
||||
if cur_season == "autumn" then
|
||||
minetest.env:remove_node(pos)
|
||||
minetest.env:add_node(pos, {name = "seasons:autumn_leaves"})
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
-- leaves fall in autumn
|
||||
minetest.register_abm({
|
||||
nodenames = {"seasons:autumn_leaves"},
|
||||
neighbors = {"air"},
|
||||
interval = 5.0,
|
||||
chance = 10,
|
||||
action = function(pos, node)
|
||||
if cur_season == "autumn" then
|
||||
local b_pos = {x = pos.x, y = pos.y - 1, z = pos.z}
|
||||
if minetest.env:get_node(b_pos).name == "air" then
|
||||
if get_season_time() > 2.3 then
|
||||
minetest.env:remove_node(pos)
|
||||
minetest.env:add_node(pos, {name = "seasons:autumn_falling_leaves"})
|
||||
nodeupdate_single(pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
local function sign(x)
|
||||
if x > 0 then
|
||||
return 1
|
||||
elseif x < 0 then
|
||||
return -1
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
-- leaves grow in spring
|
||||
-- TODO: refactor this afwul cycle
|
||||
-- (maybe) shuffle something?
|
||||
minetest.register_abm({
|
||||
nodenames = {"seasons:treehead"},
|
||||
interval = 5.0,
|
||||
chance = 10,
|
||||
action = function(pos, node)
|
||||
if cur_season == "spring" then
|
||||
--print("Spring time!")
|
||||
local modcnt = 0
|
||||
for x = -2,2 do
|
||||
for y = -1,2 do
|
||||
for z = -2,2 do
|
||||
local n_pos = {x = pos.x + x, y = pos.y + y, z = pos.z + z}
|
||||
if minetest.env:get_node(n_pos).name == "air" then
|
||||
for dx = -1,1 do
|
||||
for dy = -1,1 do
|
||||
for dz = -1,1 do
|
||||
if (math.abs(sign(dx)) + math.abs(sign(dy)) + math.abs(sign(dz)) == 1) then
|
||||
else
|
||||
local d_pos = {x = n_pos.x + dx, y = n_pos.y + dy, z = n_pos.z + dz}
|
||||
local d_node = minetest.env:get_node(d_pos)
|
||||
if d_node.name == "default:leaves" or d_node.name == "seasons:treehead" then
|
||||
if math.random(30) == 1 then
|
||||
modcnt = modcnt + 1
|
||||
minetest.env:add_node(n_pos, {name = "default:leaves"})
|
||||
if modcnt == 5 then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_abm({
|
||||
nodenames = {"default:leaves", 'default:stone', 'default:dirt', 'default:dirt_with_grass', 'default:sand', 'default:gravel', 'default:sandstone',
|
||||
'default:clay', 'default:brick', 'default:tree', 'seasons:treehead', 'default:jungletree', 'default:cactus', 'default:glass',
|
||||
'default:wood', 'default:cobble', 'default:mossycobble'},
|
||||
neighbors = {"air"},
|
||||
interval = 5.0,
|
||||
chance = 25,
|
||||
action = function(pos, node)
|
||||
if cur_season ~= "winter" then
|
||||
return
|
||||
end
|
||||
local t_pos = {x = pos.x, y = pos.y + 1, z = pos.z}
|
||||
if minetest.env:get_node(t_pos).name == "air" and minetest.env:get_node_light(t_pos, 0.5) == 15 then
|
||||
-- Grow snow!
|
||||
--if math.random(17 - math.pow(get_season_time(), 2)) == 1 then
|
||||
--print("Growing snow")
|
||||
minetest.env:add_node(t_pos, {name = 'seasons:snow', param2 = 8})
|
||||
--end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_abm({
|
||||
-- FIXME: need better way (like getting block temperature?)
|
||||
nodenames = {'default:water_source', 'seasons:ice'},
|
||||
neighbors = {"air"},
|
||||
interval = 5.0,
|
||||
chance = 5,
|
||||
action = function(pos, node)
|
||||
if cur_season ~= "winter" then
|
||||
return
|
||||
end
|
||||
local t_pos = {x = pos.x, y = pos.y + 1, z = pos.z}
|
||||
if minetest.env:get_node(t_pos).name == "air" and minetest.env:get_node_light(t_pos, 0.5) == 15 then
|
||||
-- Grow ice on water!
|
||||
--if math.random(5) == 1 then
|
||||
if node.name == "seasons:ice" then
|
||||
return
|
||||
end
|
||||
minetest.env:add_node(pos, {name = 'seasons:ice'})
|
||||
--end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
-- Remove snow which has air below it
|
||||
minetest.register_abm({
|
||||
nodenames = {"seasons:snow"},
|
||||
interval = 1.0,
|
||||
chance = 1,
|
||||
action = function(pos, node)
|
||||
local b_pos = {x = pos.x, y = pos.y - 1, z = pos.z}
|
||||
if minetest.env:get_node(b_pos).name == "air" or cur_season ~= "winter" then
|
||||
--print('Killing snow')
|
||||
minetest.env:remove_node(pos)
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_abm({
|
||||
nodenames = {"seasons:ice"},
|
||||
interval = 1.0,
|
||||
chance = 5,
|
||||
action = function(pos, node)
|
||||
if cur_season == "winter" then
|
||||
return
|
||||
end
|
||||
if get_season_time() <= 0.2 then
|
||||
-- remove ice
|
||||
--if math.random(4) == 1 then
|
||||
minetest.env:add_node(pos, {name = 'default:water_source'})
|
||||
--end
|
||||
else
|
||||
minetest.env:add_node(pos, {name = 'default:water_source'})
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_on_dignode(function(pos, oldnode, digger)
|
||||
if oldnode.name == "seasons:ice" then
|
||||
minetest.env:add_node(pos, {name = "default:water_source"})
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_abm({
|
||||
nodenames = {"default:leaves"},
|
||||
interval = 3.0,
|
||||
chance = 1,
|
||||
action = function(pos, node)
|
||||
if cur_season == "winter" then
|
||||
minetest.env:remove_node(pos)
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_abm({
|
||||
nodenames = {"seasons:autumn_leaves", "seasons:autumn_falling_leaves"},
|
||||
interval = 3.0,
|
||||
chance = 1,
|
||||
action = function(pos, node)
|
||||
if cur_season ~= "autumn" then
|
||||
minetest.env:remove_node(pos)
|
||||
end
|
||||
end
|
||||
})
|
|
@ -0,0 +1,616 @@
|
|||
--[[
|
||||
|
||||
== Introduction ==
|
||||
|
||||
Note that this requires os.clock(), debug.sethook(),
|
||||
and debug.getinfo() or your equivalent replacements to
|
||||
be available if this is an embedded application.
|
||||
|
||||
Example usage:
|
||||
|
||||
profiler = newProfiler()
|
||||
profiler:start()
|
||||
|
||||
< call some functions that take time >
|
||||
|
||||
profiler:stop()
|
||||
|
||||
local outfile = io.open( "profile.txt", "w+" )
|
||||
profiler:report( outfile )
|
||||
outfile:close()
|
||||
|
||||
== Optionally choosing profiling method ==
|
||||
|
||||
The rest of this comment can be ignored if you merely want a good profiler.
|
||||
|
||||
newProfiler(method, sampledelay):
|
||||
|
||||
If method is omitted or "time", will profile based on real performance.
|
||||
optionally, frequency can be provided to control the number of opcodes
|
||||
per profiling tick. By default this is 100000, which (on my system) provides
|
||||
one tick approximately every 2ms and reduces system performance by about 10%.
|
||||
This can be reduced to increase accuracy at the cost of performance, or
|
||||
increased for the opposite effect.
|
||||
|
||||
If method is "call", will profile based on function calls. Frequency is
|
||||
ignored.
|
||||
|
||||
|
||||
"time" may bias profiling somewhat towards large areas with "simple opcodes",
|
||||
as the profiling function (which introduces a certain amount of unavoidable
|
||||
overhead) will be called more often. This can be minimized by using a larger
|
||||
sample delay - the default should leave any error largely overshadowed by
|
||||
statistical noise. With a delay of 1000 I was able to achieve inaccuray of
|
||||
approximately 25%. Increasing the delay to 100000 left inaccuracy below my
|
||||
testing error.
|
||||
|
||||
"call" may bias profiling heavily towards areas with many function calls.
|
||||
Testing found a degenerate case giving a figure inaccurate by approximately
|
||||
20,000%. (Yes, a multiple of 200.) This is, however, more directly comparable
|
||||
to common profilers (such as gprof) and also gives accurate function call
|
||||
counts, which cannot be retrieved from "time".
|
||||
|
||||
I strongly recommend "time" mode, and it is now the default.
|
||||
|
||||
== History ==
|
||||
|
||||
2008-09-16 - Time-based profiling and conversion to Lua 5.1
|
||||
by Ben Wilhelm ( zorba-pepperfish@pavlovian.net ).
|
||||
Added the ability to optionally choose profiling methods, along with a new
|
||||
profiling method.
|
||||
|
||||
Converted to Lua 5, a few improvements, and
|
||||
additional documentation by Tom Spilman ( tom@sickheadgames.com )
|
||||
|
||||
Additional corrections and tidying by original author
|
||||
Daniel Silverstone ( dsilvers@pepperfish.net )
|
||||
|
||||
== Status ==
|
||||
|
||||
Daniel Silverstone is no longer using this code, and judging by how long it's
|
||||
been waiting for Lua 5.1 support, I don't think Tom Spilman is either. I'm
|
||||
perfectly willing to take on maintenance, so if you have problems or
|
||||
questions, go ahead and email me :)
|
||||
-- Ben Wilhelm ( zorba-pepperfish@pavlovian.net ) '
|
||||
|
||||
== Copyright ==
|
||||
|
||||
Lua profiler - Copyright Pepperfish 2002,2003,2004
|
||||
|
||||
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, 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.
|
||||
|
||||
--]]
|
||||
|
||||
|
||||
--
|
||||
-- All profiler related stuff is stored in the top level table '_profiler'
|
||||
--
|
||||
_profiler = {}
|
||||
|
||||
|
||||
--
|
||||
-- newProfiler() creates a new profiler object for managing
|
||||
-- the profiler and storing state. Note that only one profiler
|
||||
-- object can be executing at one time.
|
||||
--
|
||||
function newProfiler(variant, sampledelay)
|
||||
if _profiler.running then
|
||||
print("Profiler already running.")
|
||||
return
|
||||
end
|
||||
|
||||
variant = variant or "time"
|
||||
|
||||
if variant ~= "time" and variant ~= "call" then
|
||||
print("Profiler method must be 'time' or 'call'.")
|
||||
return
|
||||
end
|
||||
|
||||
local newprof = {}
|
||||
for k,v in pairs(_profiler) do
|
||||
newprof[k] = v
|
||||
end
|
||||
newprof.variant = variant
|
||||
newprof.sampledelay = sampledelay or 100000
|
||||
return newprof
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- This function starts the profiler. It will do nothing
|
||||
-- if this (or any other) profiler is already running.
|
||||
--
|
||||
function _profiler.start(self)
|
||||
if _profiler.running then
|
||||
return
|
||||
end
|
||||
-- Start the profiler. This begins by setting up internal profiler state
|
||||
_profiler.running = self
|
||||
self.rawstats = {}
|
||||
self.callstack = {}
|
||||
if self.variant == "time" then
|
||||
self.lastclock = os.clock()
|
||||
debug.sethook( _profiler_hook_wrapper_by_time, "", self.sampledelay )
|
||||
elseif self.variant == "call" then
|
||||
debug.sethook( _profiler_hook_wrapper_by_call, "cr" )
|
||||
else
|
||||
print("Profiler method must be 'time' or 'call'.")
|
||||
sys.exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- This function stops the profiler. It will do nothing
|
||||
-- if a profiler is not running, and nothing if it isn't
|
||||
-- the currently running profiler.
|
||||
--
|
||||
function _profiler.stop(self)
|
||||
if _profiler.running ~= self then
|
||||
return
|
||||
end
|
||||
-- Stop the profiler.
|
||||
debug.sethook( nil )
|
||||
_profiler.running = nil
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- Simple wrapper to handle the hook. You should not
|
||||
-- be calling this directly. Duplicated to reduce overhead.
|
||||
--
|
||||
function _profiler_hook_wrapper_by_call(action)
|
||||
if _profiler.running == nil then
|
||||
debug.sethook( nil )
|
||||
end
|
||||
_profiler.running:_internal_profile_by_call(action)
|
||||
end
|
||||
function _profiler_hook_wrapper_by_time(action)
|
||||
if _profiler.running == nil then
|
||||
debug.sethook( nil )
|
||||
end
|
||||
_profiler.running:_internal_profile_by_time(action)
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- This is the main by-function-call function of the profiler and should not
|
||||
-- be called except by the hook wrapper
|
||||
--
|
||||
function _profiler._internal_profile_by_call(self,action)
|
||||
-- Since we can obtain the 'function' for the item we've had call us, we
|
||||
-- can use that...
|
||||
local caller_info = debug.getinfo( 3 )
|
||||
if caller_info == nil then
|
||||
print "No caller_info"
|
||||
return
|
||||
end
|
||||
|
||||
--SHG_LOG("[_profiler._internal_profile] "..(caller_info.name or "<nil>"))
|
||||
|
||||
-- Retrieve the most recent activation record...
|
||||
local latest_ar = nil
|
||||
if table.getn(self.callstack) > 0 then
|
||||
latest_ar = self.callstack[table.getn(self.callstack)]
|
||||
end
|
||||
|
||||
-- Are we allowed to profile this function?
|
||||
local should_not_profile = 0
|
||||
for k,v in pairs(self.prevented_functions) do
|
||||
if k == caller_info.func then
|
||||
should_not_profile = v
|
||||
end
|
||||
end
|
||||
-- Also check the top activation record...
|
||||
if latest_ar then
|
||||
if latest_ar.should_not_profile == 2 then
|
||||
should_not_profile = 2
|
||||
end
|
||||
end
|
||||
|
||||
-- Now then, are we in 'call' or 'return' ?
|
||||
-- print("Profile:", caller_info.name, "SNP:", should_not_profile,
|
||||
-- "Action:", action )
|
||||
if action == "call" then
|
||||
-- Making a call...
|
||||
local this_ar = {}
|
||||
this_ar.should_not_profile = should_not_profile
|
||||
this_ar.parent_ar = latest_ar
|
||||
this_ar.anon_child = 0
|
||||
this_ar.name_child = 0
|
||||
this_ar.children = {}
|
||||
this_ar.children_time = {}
|
||||
this_ar.clock_start = os.clock()
|
||||
-- Last thing to do on a call is to insert this onto the ar stack...
|
||||
table.insert( self.callstack, this_ar )
|
||||
else
|
||||
local this_ar = latest_ar
|
||||
if this_ar == nil then
|
||||
return -- No point in doing anything if no upper activation record
|
||||
end
|
||||
|
||||
-- Right, calculate the time in this function...
|
||||
this_ar.clock_end = os.clock()
|
||||
this_ar.this_time = this_ar.clock_end - this_ar.clock_start
|
||||
|
||||
-- Now, if we have a parent, update its call info...
|
||||
if this_ar.parent_ar then
|
||||
this_ar.parent_ar.children[caller_info.func] =
|
||||
(this_ar.parent_ar.children[caller_info.func] or 0) + 1
|
||||
this_ar.parent_ar.children_time[caller_info.func] =
|
||||
(this_ar.parent_ar.children_time[caller_info.func] or 0 ) +
|
||||
this_ar.this_time
|
||||
if caller_info.name == nil then
|
||||
this_ar.parent_ar.anon_child =
|
||||
this_ar.parent_ar.anon_child + this_ar.this_time
|
||||
else
|
||||
this_ar.parent_ar.name_child =
|
||||
this_ar.parent_ar.name_child + this_ar.this_time
|
||||
end
|
||||
end
|
||||
-- Now if we're meant to record information about ourselves, do so...
|
||||
if this_ar.should_not_profile == 0 then
|
||||
local inforec = self:_get_func_rec(caller_info.func,1)
|
||||
inforec.count = inforec.count + 1
|
||||
inforec.time = inforec.time + this_ar.this_time
|
||||
inforec.anon_child_time = inforec.anon_child_time + this_ar.anon_child
|
||||
inforec.name_child_time = inforec.name_child_time + this_ar.name_child
|
||||
inforec.func_info = caller_info
|
||||
for k,v in pairs(this_ar.children) do
|
||||
inforec.children[k] = (inforec.children[k] or 0) + v
|
||||
inforec.children_time[k] =
|
||||
(inforec.children_time[k] or 0) + this_ar.children_time[k]
|
||||
end
|
||||
end
|
||||
|
||||
-- Last thing to do on return is to drop the last activation record...
|
||||
table.remove( self.callstack, table.getn( self.callstack ) )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- This is the main by-time internal function of the profiler and should not
|
||||
-- be called except by the hook wrapper
|
||||
--
|
||||
function _profiler._internal_profile_by_time(self,action)
|
||||
-- we do this first so we add the minimum amount of extra time to this call
|
||||
local timetaken = os.clock() - self.lastclock
|
||||
|
||||
local depth = 3
|
||||
local at_top = true
|
||||
local last_caller
|
||||
local caller = debug.getinfo(depth)
|
||||
while caller do
|
||||
if not caller.func then caller.func = "(tail call)" end
|
||||
if self.prevented_functions[caller.func] == nil then
|
||||
local info = self:_get_func_rec(caller.func, 1, caller)
|
||||
info.count = info.count + 1
|
||||
info.time = info.time + timetaken
|
||||
if last_caller then
|
||||
-- we're not the head, so update the "children" times also
|
||||
if last_caller.name then
|
||||
info.name_child_time = info.name_child_time + timetaken
|
||||
else
|
||||
info.anon_child_time = info.anon_child_time + timetaken
|
||||
end
|
||||
info.children[last_caller.func] =
|
||||
(info.children[last_caller.func] or 0) + 1
|
||||
info.children_time[last_caller.func] =
|
||||
(info.children_time[last_caller.func] or 0) + timetaken
|
||||
end
|
||||
end
|
||||
depth = depth + 1
|
||||
last_caller = caller
|
||||
caller = debug.getinfo(depth)
|
||||
end
|
||||
|
||||
self.lastclock = os.clock()
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- This returns a (possibly empty) function record for
|
||||
-- the specified function. It is for internal profiler use.
|
||||
--
|
||||
function _profiler._get_func_rec(self,func,force,info)
|
||||
-- Find the function ref for 'func' (if force and not present, create one)
|
||||
local ret = self.rawstats[func]
|
||||
if ret == nil and force ~= 1 then
|
||||
return nil
|
||||
end
|
||||
if ret == nil then
|
||||
-- Build a new function statistics table
|
||||
ret = {}
|
||||
ret.func = func
|
||||
ret.count = 0
|
||||
ret.time = 0
|
||||
ret.anon_child_time = 0
|
||||
ret.name_child_time = 0
|
||||
ret.children = {}
|
||||
ret.children_time = {}
|
||||
ret.func_info = info
|
||||
self.rawstats[func] = ret
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- This writes a profile report to the output file object. If
|
||||
-- sort_by_total_time is nil or false the output is sorted by
|
||||
-- the function time minus the time in it's children.
|
||||
--
|
||||
function _profiler.report( self, outfile, sort_by_total_time )
|
||||
|
||||
outfile:write
|
||||
[[Lua Profile output created by profiler.lua. Copyright Pepperfish 2002+
|
||||
|
||||
]]
|
||||
|
||||
-- This is pretty awful.
|
||||
local terms = {}
|
||||
if self.variant == "time" then
|
||||
terms.capitalized = "Sample"
|
||||
terms.single = "sample"
|
||||
terms.pastverb = "sampled"
|
||||
elseif self.variant == "call" then
|
||||
terms.capitalized = "Call"
|
||||
terms.single = "call"
|
||||
terms.pastverb = "called"
|
||||
else
|
||||
assert(false)
|
||||
end
|
||||
|
||||
local total_time = 0
|
||||
local ordering = {}
|
||||
for func,record in pairs(self.rawstats) do
|
||||
table.insert(ordering, func)
|
||||
end
|
||||
|
||||
if sort_by_total_time then
|
||||
table.sort( ordering,
|
||||
function(a,b) return self.rawstats[a].time > self.rawstats[b].time end
|
||||
)
|
||||
else
|
||||
table.sort( ordering,
|
||||
function(a,b)
|
||||
local arec = self.rawstats[a]
|
||||
local brec = self.rawstats[b]
|
||||
local atime = arec.time - (arec.anon_child_time + arec.name_child_time)
|
||||
local btime = brec.time - (brec.anon_child_time + brec.name_child_time)
|
||||
return atime > btime
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
for i=1,table.getn(ordering) do
|
||||
local func = ordering[i]
|
||||
local record = self.rawstats[func]
|
||||
local thisfuncname = " " .. self:_pretty_name(func) .. " "
|
||||
if string.len( thisfuncname ) < 42 then
|
||||
thisfuncname =
|
||||
string.rep( "-", (42 - string.len(thisfuncname))/2 ) .. thisfuncname
|
||||
thisfuncname =
|
||||
thisfuncname .. string.rep( "-", 42 - string.len(thisfuncname) )
|
||||
end
|
||||
|
||||
total_time = total_time + ( record.time - ( record.anon_child_time +
|
||||
record.name_child_time ) )
|
||||
outfile:write( string.rep( "-", 19 ) .. thisfuncname ..
|
||||
string.rep( "-", 19 ) .. "\n" )
|
||||
outfile:write( terms.capitalized.." count: " ..
|
||||
string.format( "%4d", record.count ) .. "\n" )
|
||||
outfile:write( "Time spend total: " ..
|
||||
string.format( "%4.3f", record.time ) .. "s\n" )
|
||||
outfile:write( "Time spent in children: " ..
|
||||
string.format("%4.3f",record.anon_child_time+record.name_child_time) ..
|
||||
"s\n" )
|
||||
local timeinself =
|
||||
record.time - (record.anon_child_time + record.name_child_time)
|
||||
outfile:write( "Time spent in self: " ..
|
||||
string.format("%4.3f", timeinself) .. "s\n" )
|
||||
outfile:write( "Time spent per " .. terms.single .. ": " ..
|
||||
string.format("%4.5f", record.time/record.count) ..
|
||||
"s/" .. terms.single .. "\n" )
|
||||
outfile:write( "Time spent in self per "..terms.single..": " ..
|
||||
string.format( "%4.5f", timeinself/record.count ) .. "s/" ..
|
||||
terms.single.."\n" )
|
||||
|
||||
-- Report on each child in the form
|
||||
-- Child <funcname> called n times and took a.bs
|
||||
local added_blank = 0
|
||||
for k,v in pairs(record.children) do
|
||||
if self.prevented_functions[k] == nil or
|
||||
self.prevented_functions[k] == 0
|
||||
then
|
||||
if added_blank == 0 then
|
||||
outfile:write( "\n" ) -- extra separation line
|
||||
added_blank = 1
|
||||
end
|
||||
outfile:write( "Child " .. self:_pretty_name(k) ..
|
||||
string.rep( " ", 41-string.len(self:_pretty_name(k)) ) .. " " ..
|
||||
terms.pastverb.." " .. string.format("%6d", v) )
|
||||
outfile:write( " times. Took " ..
|
||||
string.format("%4.2f", record.children_time[k] ) .. "s\n" )
|
||||
end
|
||||
end
|
||||
|
||||
outfile:write( "\n" ) -- extra separation line
|
||||
outfile:flush()
|
||||
end
|
||||
outfile:write( "\n\n" )
|
||||
outfile:write( "Total time spent in profiled functions: " ..
|
||||
string.format("%5.3g",total_time) .. "s\n" )
|
||||
outfile:write( [[
|
||||
|
||||
END
|
||||
]] )
|
||||
outfile:flush()
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- This writes the profile to the output file object as
|
||||
-- loadable Lua source.
|
||||
--
|
||||
function _profiler.lua_report(self,outfile)
|
||||
-- Purpose: Write out the entire raw state in a cross-referenceable form.
|
||||
local ordering = {}
|
||||
local functonum = {}
|
||||
for func,record in pairs(self.rawstats) do
|
||||
table.insert(ordering, func)
|
||||
functonum[func] = table.getn(ordering)
|
||||
end
|
||||
|
||||
outfile:write(
|
||||
"-- Profile generated by profiler.lua Copyright Pepperfish 2002+\n\n" )
|
||||
outfile:write( "-- Function names\nfuncnames = {}\n" )
|
||||
for i=1,table.getn(ordering) do
|
||||
local thisfunc = ordering[i]
|
||||
outfile:write( "funcnames[" .. i .. "] = " ..
|
||||
string.format("%q", self:_pretty_name(thisfunc)) .. "\n" )
|
||||
end
|
||||
outfile:write( "\n" )
|
||||
outfile:write( "-- Function times\nfunctimes = {}\n" )
|
||||
for i=1,table.getn(ordering) do
|
||||
local thisfunc = ordering[i]
|
||||
local record = self.rawstats[thisfunc]
|
||||
outfile:write( "functimes[" .. i .. "] = { " )
|
||||
outfile:write( "tot=" .. record.time .. ", " )
|
||||
outfile:write( "achild=" .. record.anon_child_time .. ", " )
|
||||
outfile:write( "nchild=" .. record.name_child_time .. ", " )
|
||||
outfile:write( "count=" .. record.count .. " }\n" )
|
||||
end
|
||||
outfile:write( "\n" )
|
||||
outfile:write( "-- Child links\nchildren = {}\n" )
|
||||
for i=1,table.getn(ordering) do
|
||||
local thisfunc = ordering[i]
|
||||
local record = self.rawstats[thisfunc]
|
||||
outfile:write( "children[" .. i .. "] = { " )
|
||||
for k,v in pairs(record.children) do
|
||||
if functonum[k] then -- non-recorded functions will be ignored now
|
||||
outfile:write( functonum[k] .. ", " )
|
||||
end
|
||||
end
|
||||
outfile:write( "}\n" )
|
||||
end
|
||||
outfile:write( "\n" )
|
||||
outfile:write( "-- Child call counts\nchildcounts = {}\n" )
|
||||
for i=1,table.getn(ordering) do
|
||||
local thisfunc = ordering[i]
|
||||
local record = self.rawstats[thisfunc]
|
||||
outfile:write( "children[" .. i .. "] = { " )
|
||||
for k,v in record.children do
|
||||
if functonum[k] then -- non-recorded functions will be ignored now
|
||||
outfile:write( v .. ", " )
|
||||
end
|
||||
end
|
||||
outfile:write( "}\n" )
|
||||
end
|
||||
outfile:write( "\n" )
|
||||
outfile:write( "-- Child call time\nchildtimes = {}\n" )
|
||||
for i=1,table.getn(ordering) do
|
||||
local thisfunc = ordering[i]
|
||||
local record = self.rawstats[thisfunc];
|
||||
outfile:write( "children[" .. i .. "] = { " )
|
||||
for k,v in pairs(record.children) do
|
||||
if functonum[k] then -- non-recorded functions will be ignored now
|
||||
outfile:write( record.children_time[k] .. ", " )
|
||||
end
|
||||
end
|
||||
outfile:write( "}\n" )
|
||||
end
|
||||
outfile:write( "\n\n-- That is all.\n\n" )
|
||||
outfile:flush()
|
||||
end
|
||||
|
||||
-- Internal function to calculate a pretty name for the profile output
|
||||
function _profiler._pretty_name(self,func)
|
||||
|
||||
-- Only the data collected during the actual
|
||||
-- run seems to be correct.... why?
|
||||
local info = self.rawstats[ func ].func_info
|
||||
-- local info = debug.getinfo( func )
|
||||
|
||||
local name = ""
|
||||
if info.what == "Lua" then
|
||||
name = "L:"
|
||||
end
|
||||
if info.what == "C" then
|
||||
name = "C:"
|
||||
end
|
||||
if info.what == "main" then
|
||||
name = " :"
|
||||
end
|
||||
|
||||
if info.name == nil then
|
||||
name = name .. "<"..tostring(func) .. ">"
|
||||
else
|
||||
name = name .. info.name
|
||||
end
|
||||
|
||||
if info.source then
|
||||
name = name .. "@" .. info.source
|
||||
else
|
||||
if info.what == "C" then
|
||||
name = name .. "@?"
|
||||
else
|
||||
name = name .. "@<string>"
|
||||
end
|
||||
end
|
||||
name = name .. ":"
|
||||
if info.what == "C" then
|
||||
name = name .. "?"
|
||||
else
|
||||
name = name .. info.linedefined
|
||||
end
|
||||
|
||||
return name
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- This allows you to specify functions which you do
|
||||
-- not want profiled. Setting level to 1 keeps the
|
||||
-- function from being profiled. Setting level to 2
|
||||
-- keeps both the function and its children from
|
||||
-- being profiled.
|
||||
--
|
||||
-- BUG: 2 will probably act exactly like 1 in "time" mode.
|
||||
-- If anyone cares, let me (zorba) know and it can be fixed.
|
||||
--
|
||||
function _profiler.prevent(self, func, level)
|
||||
self.prevented_functions[func] = (level or 1)
|
||||
end
|
||||
|
||||
|
||||
_profiler.prevented_functions = {
|
||||
[_profiler.start] = 2,
|
||||
[_profiler.stop] = 2,
|
||||
[_profiler._internal_profile_by_time] = 2,
|
||||
[_profiler._internal_profile_by_call] = 2,
|
||||
[_profiler_hook_wrapper_by_time] = 2,
|
||||
[_profiler_hook_wrapper_by_call] = 2,
|
||||
[_profiler.prevent] = 2,
|
||||
[_profiler._get_func_rec] = 2,
|
||||
[_profiler.report] = 2,
|
||||
[_profiler.lua_report] = 2,
|
||||
[_profiler._pretty_name] = 2
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 177 B |
Binary file not shown.
After Width: | Height: | Size: 222 B |
|
@ -0,0 +1 @@
|
|||
0
|
Loading…
Reference in New Issue