#include "world.hpp" #include "pprint.hpp" #include "serializelua.hpp" #include LuaTokenConstant(tangible_auto, "auto", ""); static void tangible_getall(LuaCoreStack &LS0, LuaSlot list, const util::IdVector &idv) { LuaVar tangibles, tan; LuaExtStack LS(LS0.state(), tangibles, tan); LS.rawget(tangibles, LuaRegistry, "tangibles"); assert(LS.istable(tangibles)); LS.set(list, LuaNewTable); int index = 1; for (int64_t id : idv) { LS.rawget(tan, tangibles, id); assert(LS.istable(tan)); LS.rawset(list, index++, tan); } } LuaDefine(tangible_xyz, "tan", "|Get the current coordinates of the tangible and the plane." "|Returns four values: x, y, z, plane") { LuaArg tanobj; LuaRet x, y, z, plane; LuaDefStack LS(L, tanobj, x, y, z, plane); World *w = World::fetch_global_pointer(L); Tangible *tan = w->tangible_get(LS, tanobj, false); AnimCoreState pos = tan->anim_queue_.get_final_core_state(); LS.set(x, pos.xyz.x); LS.set(y, pos.xyz.y); LS.set(z, pos.xyz.z); LS.set(plane, pos.plane); return LS.result(); } LuaDefine(tangible_animdebug, "tan", "|Return a debug string showing the entire animation queue" "|") { LuaArg tanobj; LuaRet result; LuaDefStack LS(L, tanobj, result); World *w = World::fetch_global_pointer(L); Tangible *tan = w->tangible_get(LS, tanobj, false); LS.set(result, tan->anim_queue_.steps_debug_string()); return LS.result(); } LuaDefine(tangible_animfinal, "tan", "|Return the final step in the animation queue." "|" "|The animation queue stores animation steps. This function returns" "|the final animation step. An animation step consists of key-value" "|pairs. Some of those key-value pairs describe the last thing that" "|happened to the tangible. Others describe the final resting place" "|of the tangible." "|" "|For example, if the tangible were a pirate chest, the key-value" "|pairs might be:" "|" "| action='open' # last thing the chest did" "| xyz={1,2,3} # xyz coordinate" "| plane='earth' # plane where the chest is located" "| facing=0 # rotation of the chest" "| open=true # chest can be open or closed" "| fullness=0.8 # how big the heap of coins is" "|" "|See doc(tangible.animinit) for more information about animation queues." "|") { LuaArg tanobj; LuaRet result; LuaDefStack LS(L, tanobj, result); World *w = World::fetch_global_pointer(L); Tangible *tan = w->tangible_get(LS, tanobj, false); AnimState state = tan->anim_queue_.get_final_everything(); state.to_lua(LS, result, true, true); return LS.result(); } LuaDefine(tangible_animinit, "tan,config", "|Reinitialize the animation queue and specify persistent state." "|" "|Every tangible has an animation queue. The queue consists of a" "|sequence of animation steps. Each step consists of a list of" "|key-value pairs. For example, if you want a human person to jump" "|three feet in the air, you might find this animation step in the" "|animation queue:" "|" "| action='jump' - the name of the animation to perform" "| height=3.0 - the height to which you want him to jump" "| xyz={1,2,3} - person's xyz coordinate during the jump" "| plane=earth - plane where the jump takes place" "|" "|Some of those key-value pairs are 'persistent'. For example, xyz is" "|persistent. That means that the player must always have an xyz" "|coordinate. Every single animation step in the queue must" "|contain a value for xyz. Likewise, 'plane' is a persistent variable." "|The player must always be on some plane or another." "|" "|When you add an animation step to the animation queue, you do not have" "|to always specify xyz and plane. For example, you can legally say:" "|" "| tangible.animate(a, nil, {action='jump', height=3.0}))" "|" "|This adds a step to the animation queue. That step contains" "|xyz and plane, even though we didn't specify xyz and plane in" "|the 'animate' command above. The values for xyz and plane will be" "|copied over from the previous animation step. In this way, those values" "|get persisted: they stay the same unless you change them in" "|the 'animate' command." "|" "|There are four hardwired persistent variables: plane,xyz,facing,bp." "|These variables are persistent no matter what. This function," "|tangible.animinit, optionally allows you to create more persistent" "|variables. For example, let's say you have a pirate chest. You might" "|want to add two persistent variables in addition to the usual set:" "|" "| open=true - whether the chest is open or closed" "| heapsize=0.8 - the size of the heap of coins" "|" "|Making a variable persistent means that it will always have a value" "|no matter what you do." "|" "|This function, tangible.animinit, is used to reconfigure the set of" "|persistent state variables that are retained by the tangible's" "|animation queue. You must provide a table containing all the" "|persistent values you want, and their initial values." "|") { LuaArg tanobj, config; LuaDefStack LS(L, tanobj, config); World *w = World::fetch_global_pointer(L); Tangible *tan = w->tangible_get(LS, tanobj, false); AnimState state; eng::string error = state.from_lua(LS, config, true, false); if (!error.empty()) { luaL_error(L, "%s", error.c_str()); } AnimState defsource = tan->anim_queue_.get_final_persistent(); error = state.add_defaults(&defsource); if (!error.empty()) { luaL_error(L, "%s", error.c_str()); } tan->anim_queue_.clear(state); tan->update_plane_item(); return LS.result(); } LuaDefine(tangible_animate, "tan,options,config", "|Add an animation step to the tangible." "|" "|The animation queue stores animation steps. This function, " "|tangible.animate, adds one new animation step to the queue." "|" "|It might be useful to read doc(tangible.animinit) before reading" "|more." "|" "|An animation step is just a list of key-value pairs. Therefore," "|the config table must be key-value pairs. Keys must be simple" "|identifiers. Values can be numbers, strings, booleans, or" "|coordinates." "|" "|Some of the key-value pairs may match the name of a persistent" "|variable. If so, that key-value pair permanently changes the" "|value of that persistent variable. The new value will" "|be retained for all future animation steps." "|" "|Some of the key-value pairs may not match the name of any persistent" "|variable. If so, that key-value pair is part of the" "|animation step, but nothing is propagated forward to future animation" "|steps." "|" "|The options can be nil, or options can be a table containing" "|the following flags:" "|" "| replace: if true, then the last step in the queue is removed," "| and the new animation replaces it. Persistent state is carried" "| over from the step that was replaced." "|" "|") { LuaArg tanobj, options, steptab; LuaVar option; LuaDefStack LS(L, option, tanobj, options, steptab); bool replace = false; if (!LS.isnil(options)) { LuaKeywordParser kp(LS, options); if (kp.optional(option, "replace")) { replace = LS.ckboolean(option); } kp.final_check_throw(); } World *w = World::fetch_global_pointer(L); Tangible *tan = w->tangible_get(LS, tanobj, false); AnimState previous = tan->anim_queue_.get_final_persistent(); AnimState update; eng::string error = update.from_lua(LS, steptab, false, true); if (!error.empty()) { luaL_error(L, "%s", error.c_str()); } AnimState merge; error = merge.merge(previous, update); if (!error.empty()) { luaL_error(L, "%s", error.c_str()); } if (replace) { tan->anim_queue_.replace(merge); } else { tan->anim_queue_.add(merge); } tan->update_plane_item(); return LS.result(); } LuaDefine(tangible_setclass, "tan,class", "|Set the class of the tangible." "|" "|The class can be a 'class table' (ie, a table of methods), " "|or it can be a string that names a class. The tangible is " "|given an __index metamethod that points at the class table." "|") { LuaArg tanobj, classname; LuaVar classtab, mt; LuaDefStack LS(L, tanobj, classname, classtab, mt); World *w = World::fetch_global_pointer(L); w->tangible_get(LS, tanobj, false); eng::string err = LS.getclass(classtab, classname); if (err != "") { luaL_error(L, "%s", err.c_str()); } LS.getmetatable(mt, tanobj); LS.rawset(mt, "__index", classtab); return LS.result(); } LuaDefine(tangible_getclassname, "tan", "|Get the classname of the tangible, if any." "|" "|The return value is a string (or nil)." "|") { LuaArg tanobj; LuaRet classname; LuaDefStack LS(L, tanobj, classname); World *w = World::fetch_global_pointer(L); w->tangible_get(LS, tanobj, false); eng::string name = LS.classname(tanobj); if (name == "") { LS.set(classname, LuaNil); } else { LS.set(classname, name); } return LS.result(); } LuaDefine(tangible_getclass, "tan", "|Get the class of the tangible, if any." "|" "|The return value is a class table (or nil)." "|") { LuaArg tanobj; LuaRet classtab; LuaDefStack LS(L, tanobj, classtab); World *w = World::fetch_global_pointer(L); w->tangible_get(LS, tanobj, false); LS.tangetclass(classtab, tanobj); return LS.result(); } LuaDefine(tangible_delete, "tan", "|Delete the specified tangible." "|" "|This cannot be used to delete player tangibles," "|To delete a player, use tangible.redirect") { LuaArg tanobj; LuaDefStack LS(L, tanobj); World *w = World::fetch_global_pointer(L); Tangible *tan = w->tangible_get(LS, tanobj, true); if (tan == nullptr) { return LS.result(); } if (tan->is_an_actor()) { luaL_error(L, "Cannot delete a player using tangible.delete, use tangible.redirect instead."); return 0; } w->tangible_delete(tan->id()); return LS.result(); } LuaDefine(tangible_build, "config", "|Build a new tangible object." "|" "|The configuration table must contain the keyword 'class', which" "|must be the name of a class created with 'makeclass'. It may also" "|contain the following values:" "|" "| bp - the unreal blueprint, defaults to class name." "| plane - the plane, defaults to actor.plane" "| xyz - the xyz coordinate, defaults to actor.xyz" "| facing - the rotation, defaults to actor.facing" "|" "|Tangible.build will create an initial animstate containing only" "|bp, plane, xyz, and facing." "|" "|After creating the tangible and setting up the initial animation" "|state, build will call the constructor for the tangible." "|The constructor must have this prototype:" "|" "| function myclass.init(place, actor, config)" "|" "|The configuration table passed to tangible.build is then passed" "|directly to the init function. The constructor should use the keywords" "|module to parse the keyword arguments." "|" "|The constructor is not allowed to block." ){ LuaArg config; LuaVar classname, classtab, bp, plane, xyz, facing, mt, func; LuaRet database; LuaDefStack LS(L, config, classname, classtab, bp, plane, xyz, facing, mt, func, database); World *w = World::fetch_global_pointer(L); LuaKeywordParser kp(LS, config); kp.required(classname, "class"); kp.optional(bp, "bp"); kp.optional(plane, "plane"); kp.optional(xyz, "xyz"); kp.optional(facing, "facing"); kp.check_throw(); // Verify the class. eng::string err = LS.getclass(classtab, classname); if (err != "") { luaL_error(L, "%s", err.c_str()); } // Calculate the initial animation state. AnimState state; if (!LS.isnil(bp)) { state.set_string("bp", LS.ckstring(bp)); } else { state.set_string("bp", LS.classname(classtab)); } if (!LS.isnil(plane)) { state.set_string("plane", LS.ckstring(plane)); } if (!LS.isnil(xyz)) { state.set_dxyz("xyz", LS.ckxyz(xyz)); } if (!LS.isnil(facing)) { state.set_number("facing", LS.cknumber(facing)); } // Add default values from the actor. Set persistent flags. Tangible *actor = w->tangible_get(w->lthread_actor_id_); AnimState actorstate = actor->anim_queue_.get_final_persistent(); err = state.add_defaults(&actorstate); if (err != "") { luaL_error(L, "%s", err.c_str()); } int64_t new_id = w->alloc_id_predictable(); Tangible *tan = w->tangible_make(LS, database, new_id); // Update the class of the new tangible. LS.getmetatable(mt, database); LS.rawset(mt, "__index", classtab); // Initialize the animstate of the new tangible. tan->anim_queue_.clear(state); tan->update_plane_item(); // Call the constructor and finish. LS.rawget(func, classtab, "init"); if (!LS.isfunction(func)) { return LS.result(); } return LS.tailcall(false, func, database, config); } LuaDefine(tangible_get, "id", "|Get the tangible with the specified id." "|This is for debugging only and will be removed in" "|the released version.") { LuaArg id; LuaVar tangibles; LuaRet database; LuaDefStack LS(L, id, tangibles, database); int64_t nid = LS.ckinteger(id); LS.rawget(tangibles, LuaRegistry, "tangibles"); LS.rawget(database, tangibles, id); if (!LS.istable(database)) { luaL_error(L, "Not a tangible ID: %d", nid); } return LS.result(); } LuaDefine(tangible_redirect, "tan1,tan2,bulldozetan1", "|Redirect is not working yet") { LuaArg actor1, actor2, bldz; LuaDefStack LS(L, actor1, actor2, bldz); World *w = World::fetch_global_pointer(L); bool bulldoze = LS.ckboolean(bldz); Tangible *tan1 = w->tangible_get(LS, actor1, false); if (!tan1->is_an_actor()) { luaL_error(L, "redirect source is not an actor"); } if (LS.isnil(actor2)) { w->redirects_[tan1->id()] = 0; } else { Tangible *tan2 = w->tangible_get(LS, actor2, false); tan2->configure_id_pool_for_actor(); w->redirects_[tan1->id()] = tan2->id(); } if (bulldoze) { w->tangible_delete(tan1->id()); } return LS.result(); } LuaDefine(tangible_id, "tan", "|Return the tangible's id number." "|This is for debugging only and will be removed" "|in the released version.") { LuaArg tanobj; LuaRet id; LuaDefStack LS(L, tanobj, id); int64_t tid = LS.tanid(tanobj); if (tid == 0) { luaL_error(L, "Not a tangible"); } LS.set(id, tid); return LS.result(); } LuaDefine(tangible_actor, "", "|Return the current actor.") { LuaRet actor; LuaVar tangibles; LuaDefStack LS(L, tangibles, actor); World *w = World::fetch_global_pointer(L); LS.rawget(tangibles, LuaRegistry, "tangibles"); LS.rawget(actor, tangibles, w->lthread_actor_id_); return LS.result(); } LuaDefine(tangible_place, "", "|Return the current place.") { LuaRet place; LuaVar tangibles; LuaDefStack LS(L, tangibles, place); World *w = World::fetch_global_pointer(L); LS.rawget(tangibles, LuaRegistry, "tangibles"); LS.rawget(place, tangibles, w->lthread_place_id_); return LS.result(); } LuaDefine(tangible_find, "config", "|Find tangibles by their location." "|" "|There are multiple ways to specify the plane, the bounding" "|box, and the shape of the search. The most basic is to" "|include these parameters in the table:" "|" "| plane : the plane to search (a string)" "| center : xyz of the center of the search (a vector)" "| radius : the radius of the search (a vector or number)" "| shape : 'box', 'sphere', or 'cylinder'" "|" "|Shape has a default: 'sphere'. The other parameters do" "|not have default values." "|" "|If you specify the radius as a vector, ie, different radii in" "|each dimension, then the 'sphere' shape will actually be a" "|spheroid, and the 'cylinder' shape will actually be a cylindroid." "|" "|Instead of specifying the center and plane, you can specify" "|a tangible to search near:" "|" "| near : a tangible in whose vicinity the search occurs" "|" "|If you specify 'near', then by default, the near tangible is" "|filtered out of the search. If you want to include it in the" "|results, set the 'include' flag to true." "|" "| include : include the 'near' object in the results" "|" "|If you want to search an entire plane, you can use the" "|wholeplane option, which replaces all the other options:" "|" "| wholeplane: the name of the plane to scan in its entirety" "|" "|It is valid to use 0.0 as a radius. For example, you could" "|use shape='cylinder' and radiusz=0.0 to scan a flat" "|circular area." "|" "|It is also valid to use math.huge (infinity) as a radius. For" "|example, you could use shape='cylinder' and radius.z=math.huge" "|to scan an infinitely tall cylinder." "|" "|If you are making a 2D game, it works fine to set all object Z" "|coordinates to zero, and then do searches with centerz=0.0" "|and radiusz=0.0." "|") { LuaArg config; LuaRet result; LuaDefStack LS(L, config, result); LuaKeywordParser kw(LS, config); PlaneScan scan; scan.configure(kw); kw.final_check_throw(); World *w = World::fetch_global_pointer(L); util::IdVector idv; w->get_near(scan, &idv); tangible_getall(LS, result, idv); return LS.result(); } LuaDefine(tangible_start, "tangible,function,arg1,arg2...", "|Start a thread." "|" "|Every thread is owned by a tangible. The first argument" "|to 'tangible.start' indicates the tangible that owns" "|the new thread. Instead of passing a single tangible," "|you can pass a list of tangibles, in which case a thread" "|is started on each tangible." "|" "|The function can be a lua closure, or it can be a string." "|If it's a string, then the tangible's class will be" "|used to look up the relevant closure." "|" "|The arguments arg1,arg2... will be passed to the" "|function." "|" "|Actor and place aren't passed to the function unless" "|you manually include them in the list arg1, arg2, etc." "|The new thread can, however, use the builtin " "|functions 'tangible.actor' and 'tangible.place' to obtain " "|actor and place. Actor will be the same actor who " "|called 'tangible.start'. Place will be the tangible that" "|owns the thread, ie, the tangible passed to 'tangible.start'." "|" "|The new thread doesn't start running instantly:" "|it waits until the current thread is finished. The" "|current thread must either block (eg, 'wait') or terminate" "|before the new thread can actually begin execution." "|" "|If you start a thread, then start another, then both of" "|the newly-started threads wait until the current thread" "|is finished. At that point, it is undefined which of the" "|two new threads runs first." "|" "|Threads are owned by tangibles. If a tangible is" "|deleted, then none of its threads will ever be resumed," "|for any reason." "|" "|However, if a thread deletes its own place (ie, the tangible" "|that owns the thread), then the thread will be allowed" "|to continue running until it blocks. But from that point" "|forward, the thread will never be resumed for any reason.") { int top = lua_gettop(L); if (top < 2) { luaL_error(L, "Not enough arguments to tangible.start"); return 0; } int varlen = top - 2; World *w = World::fetch_global_pointer(L); w->guard_blockable(L, "tangible.start"); LuaVar mt, classtab, plthreads, thread, thinfo, func, tanlist; LuaDefStack LS(L, mt, classtab, plthreads, thread, thinfo, func, tanlist); LuaSpecial place(1); LuaSpecial fname(2); // If they passed in a single tangible, convert it to a tangible list. int64_t place_id = LS.tanid(place); if (place_id != 0) { LS.newtable(tanlist); LS.rawset(tanlist, 1, place); } else { LS.set(tanlist, place); if (!LS.istable(tanlist)) { luaL_error(L, "tangible.start expects a tangible or list of tangibles"); return 0; } } // Check the entire tangible list for validity. for (int i = 1; ; i++) { LS.rawget(place, tanlist, i); if (LS.isnil(place)) break; w->tangible_get(LS, place, false); } // Start threads on all the tangibles. for (int i = 1; ; i++) { LS.rawget(place, tanlist, i); if (LS.isnil(place)) break; // Confirm that the place is a valid tangible, // and get the tangible ID. place_id = LS.tanid(place); // Get place's metatable and threads table. LS.getmetatable(mt, place); assert(LS.istable(mt)); LS.rawget(plthreads, mt, "threads"); assert(LS.istable(plthreads)); // Get the function closure. if (LS.isfunction(fname)) { LS.set(func, fname); } else if (LS.isstring(fname)) { LS.rawget(classtab, mt, "__index"); assert(LS.istable(classtab)); LS.rawget(func, classtab, fname); if (!LS.isfunction(func)) { eng::string cfname = LS.ckstring(fname); luaL_error(L, "tangible doesn't have method: %s", cfname.c_str()); return 0; } } else { luaL_error(L, "invalid function, expected closure or string"); return 0; } // Create a new thread, set up function and arguments. lua_State *CO = LS.newthread(thread); lua_pushvalue(L, func.index()); for (int i = 0; i < varlen; i++) { lua_pushvalue(L, i + 3); } lua_xmove(L, CO, varlen + 1); // Create the thread info table. LS.newtable(thinfo); LS.rawset(thinfo, "thread", thread); LS.rawset(thinfo, "actorid", w->lthread_actor_id_); LS.rawset(thinfo, "isnew", true); LS.rawset(thinfo, "useppool", false); LS.rawset(thinfo, "print", false); // Get a thread ID for the new thread, store it in // the thread table. int64_t tid = w->alloc_id_predictable(); LS.rawset(plthreads, tid, thinfo); // Push the thread's ID into the runnable thread queue. w->schedule(0, tid, place_id); } return LS.result(); } LuaDefine(time, "", "|Get the elapsed time in ticks since the world started running.") { World *w = World::fetch_global_pointer(L); LuaRet time; LuaDefStack LS(L, time); LS.set(time, w->clock_); return LS.result(); } LuaDefine(wait, "nticks", "|Wait the specified number of ticks.") { World *w = World::fetch_global_pointer(L); w->guard_blockable(L, "wait"); // Parse the argument. LuaArg seconds; LuaDefStack LS(L, seconds); int64_t n = LS.ckinteger(seconds); if ((n < 0) || (n > 1000000)) { luaL_error(L, "Argument to wait must be between 0 and 1000000"); return LS.result(); } // Schedule a continuation. w->schedule(w->clock_ + n, w->lthread_thread_id_, w->lthread_place_id_); return lua_yield(L, 0); } LuaDefine(nopredict, "", "|Stop predictive execution of this thread.") { World *w = World::fetch_global_pointer(L); w->guard_nopredict(L, "nopredict"); if (lua_gettop(L) != 0) { luaL_error(L, "nopredict takes no arguments"); } return 0; } LuaDefine(math_random, "(args...)", "|Generate random numbers." "|" "|What it generates depends on the arguments:" "|" "| () - a float in range [0.0, 1.0)" "| (high) - an int between 1 and high inclusive" "| (low, high) - an int between low and high inclusive" "|" "|You may also pass in a randomstate table" "|as an optional first argument." "|" "|math.random tries to cooperate with predictive" "|reexecution to be as predictable as possible." "|To achieve predictability, we used an ad-hoc" "|random number generator. It passes a variety of" "|statistical tests, but it's not well-studied." "|" "|If you want actually want nonpredictability, or" "|if you need the assurance of a well-studied random" "|number generator, use math.mtrandom or" "|math.cryptrandom instead.") { // Parse the arguments. // This is hairy because there's a lot of possibilities. bool passed_in_randomstate = false; int arg = 1; if ((lua_gettop(L) >= arg) && (lua_istable(L, arg))) { passed_in_randomstate = true; arg += 1; } bool have_range = false; int64_t low, high; if ((lua_gettop(L) >= arg) && (lua_type(L, arg) == LUA_TNUMBER)) { double lowf, highf; if ((lua_gettop(L) >= arg+1) && (lua_type(L, arg+1) == LUA_TNUMBER)) { lowf = std::floor(lua_tonumber(L, arg)); highf = std::floor(lua_tonumber(L, arg + 1)); arg += 2; } else { lowf = 1; highf = std::floor(lua_tonumber(L, arg)); arg += 1; } if ((lowf < -LuaCoreStack::MAXINT) || (highf > LuaCoreStack::MAXINT)) { luaL_error(L, "math.random range exceeds MAXINT"); return 0; } if (lowf > highf) { luaL_error(L, "math.random range low > high"); return 0; } low = int64_t(lowf); high = int64_t(highf); have_range = true; } if (lua_gettop(L) >= arg) { luaL_error(L, "math.random accepts an optional randomstate and an optional range"); return 0; } // Generate the seed, count, and salt. // The salt prevents accidental duplication between user-specified // seeds and system-generated seeds. uint64_t seed, count, salt; if (passed_in_randomstate) { lua_pushstring(L, "seed"); lua_rawget(L, 1); lua_pushstring(L, "count"); lua_rawget(L, 1); if ((lua_type(L, -1) != LUA_TNUMBER) || (lua_type(L, -2) != LUA_TNUMBER)) { luaL_error(L, "Not a valid randomstate table"); return 0; } double dseed = lua_tonumber(L, -2); double dcount = lua_tonumber(L, -1); seed = uint64_t(dseed) & LuaCoreStack::MAXINT; count = uint64_t(dcount) & LuaCoreStack::MAXINT; if (dseed < 0) { salt = 0x35c9a6082a097ade; } else { salt = 0x4785d086ead90c20; } lua_pop(L, 2); lua_pushstring(L, "count"); lua_pushnumber(L, double((count + 1) & LuaCoreStack::MAXINT)); lua_rawset(L, 1); } else { World *w = World::fetch_global_pointer(L); if (w->lthread_use_ppool_) { Tangible *actor = w->tangible_get(w->lthread_actor_id_); seed = w->lthread_actor_id_; count = actor->id_player_pool_.get_seqno(); salt = 0x3ab0fb84aedc3764; } else { // TODO: maybe throw in a 'donotpredict' here. seed = 123456; count = w->id_global_pool_.get_seqno(); salt = 0x6f493c90faf0139d; } } if (!have_range) { // Generate the hash and convert to a double. uint64_t hash = util::hash_ints(seed, count, salt, 456); lua_pushnumber(L, util::hash_to_double(hash)); } else { // Generate the hash and scale it into the desired range. // This code is not quite right: the results are not quite // uniform, this is especially true for very long ranges. uint64_t hash = util::hash_ints(seed, count, salt, 456); uint64_t range = (high - low) + 1; uint64_t offset = (hash & 0x7FFFFFFFFFFFFFFF) % range; int64_t result = low + int64_t(offset); lua_pushnumber(L, result); } return 1; } LuaDefine(math_randomstate, "seed", "|Create and return a randomstate table." "|This is a lua table that stores the state for a random" "|number generator. A randomstate table can be passed" "|to math.random." "|" "|You can optionally omit the seed, in which case a" "|seed will be chosen randomly. Automatically-generated" "|seeds are guaranteed never to be the same as" "|user-specified seeds.") { double seed; if (lua_gettop(L) == 0) { World *w = World::fetch_global_pointer(L); int64_t iseed = (w->id_global_pool_.get_seqno() & LuaCoreStack::MAXINT) + 1; seed = -iseed; } else if (lua_gettop(L) == 1) { if (lua_type(L, 1) != LUA_TNUMBER) { luaL_error(L, "math.randomstate takes an optional integer seed"); return 0; } seed = lua_tonumber(L, 1); if ((seed < 0.0) || (seed > LuaCoreStack::MAXINT) || (std::floor(seed) != seed)) { luaL_error(L, "math.randomstate seed must be an integer 0-MAXINT"); return 0; } } else { luaL_error(L, "math.randomstate takes an optional integer seed"); return 0; } lua_newtable(L); lua_pushstring(L, "seed"); lua_pushnumber(L, seed); lua_rawset(L, -3); lua_pushstring(L, "count"); lua_pushnumber(L, 0); lua_rawset(L, -3); return 1; } LuaSandboxBuiltin(math_randomseed, "", ""); LuaDefine(pprint, "obj1, obj2, ...", "|Pretty-print the specified objects." "|" "|See also: pprintx, which has a lot more options." "|This function uses the default options: pretty print indented," "|start at indentation level zero, and always expand the" "|top-level table." "|") { World *w = World::fetch_global_pointer(L); std::ostream *ostream = w->lthread_print_stream(); LuaExtraArgs extra; LuaDefStack LS(L, extra); for (int i = 0; i < extra.size(); i++) { pprint(LS, extra[i], PrettyPrintOptions(), ostream); (*ostream) << std::endl; } return LS.result(); } LuaDefine(pprintx, "options", "|Pretty-print the specified object, with options" "|" "|Options is a table with these fields:" "|" "| value - the object to pretty-print" "| indent - if false, suppress newlines and indentation (default: true)" "| level - base level of indentation (default: zero)" "| expand - if true, force expansion of top-level table (default: true)" "|" "|About the expand flag: normally, when you print a class, it just " "|prints '', and when you print a tangible, it just" "|prints ''. But sometimes, you want to see the details." "|The expand flag forces it to expand the top-level table, even if the" "|top-level table is a tangible or class." "|") { World *w = World::fetch_global_pointer(L); std::ostream *ostream = w->lthread_print_stream(); LuaArg loptions; LuaVar value; LuaDefStack LS(L, loptions, value); PrettyPrintOptions options; LuaKeywordParser kp(LS, loptions); options.parse(kp); if (!kp.optional(value, "value")) { LS.set(value, LuaNil); } kp.final_check_throw(); pprint(LS, value, options, ostream); return LS.result(); } LuaDefine(print, "obj1, obj2, ...", "|Print object or objects.") { World *w = World::fetch_global_pointer(L); std::ostream *ostream = w->lthread_print_stream(); LuaCoreStack LS(L); int n = lua_gettop(L); for (int i = 1; i <= n; i++) { LuaSpecial root(i); atomic_print(LS, root, false, ostream); if (i < n) (*ostream) << " "; } (*ostream) << std::endl; return 0; } LuaDefine(dprint, "obj1, obj2, ...", "|Print object or objects on the debug console.") { std::ostringstream oss; LuaCoreStack LS(L); int n = lua_gettop(L); for (int i = 1; i <= n; i++) { LuaSpecial root(i); atomic_print(LS, root, false, &oss); if (i < n) oss << " "; } oss << std::endl; util::dprintview(oss.str()); return 0; } LuaDefine(doc, "function", "|Print documentation for specified function.") { World *w = World::fetch_global_pointer(L); std::ostream *ostream = w->lthread_print_stream(); LuaArg func; LuaDefStack LS(L, func); bool ok = w->get_source().function_docs(LS, func, *ostream); if (!ok) { (*ostream) << "No documentation found."; } return LS.result(); } LuaDefine(docsearch, "search-string", "Search the docs for the specified string") { World *w = World::fetch_global_pointer(L); std::ostream *ostream = w->lthread_print_stream(); LuaArg ss; LuaDefStack LS(L, ss); eng::string searchstring = LS.ckstring(ss); bool ok = w->get_source().search_docs(searchstring, *ostream); if (!ok) { (*ostream) << "No documentation found."; } return LS.result(); } int lfn_http_request(lua_State *L, const char *method) { World *w = World::fetch_global_pointer(L); w->guard_blockable(L, "http.get"); LuaArg request; LuaRet response; LuaDefStack LS(L, request, response); LuaKeywordParser kp(LS, request); HttpClientRequest req; // Parse the request and make sure it's valid. // If not, immediately pass a '400 bad request' back to lua. req.set_method(method); req.configure(kp); kp.final_check_throw(); req.set_defaults(); eng::string error = req.check(); if (!error.empty()) { HttpParser::store_fail(LS, response, 400, util::ss("Bad Request: ", error)); return LS.result(); } // Give the request an ID. req.set_request_id(w->id_global_pool_.get_one()); req.set_place_id(w->lthread_place_id_); req.set_thread_id(w->lthread_thread_id_); // Store it in the global request table. w->http_requests_[req.request_id()] = req; // Block. return lua_yield(L, 0); } LuaDefine(http_get, "request", "|Make an HTTP GET request. Returns an HTTP response." "|See doc(http.clientrequest) and doc(http.clientresponse).") { return lfn_http_request(L, "GET"); } LuaDefine(http_head, "request", "|Make an HTTP HEAD request. Returns an HTTP response." "|See doc(http.clientrequest) and doc(http.clientresponse).") { return lfn_http_request(L, "HEAD"); } LuaDefine(http_post, "request", "|Make an HTTP POST request. Returns an HTTP response." "|See doc(http.clientrequest) and doc(http.clientresponse).") { return lfn_http_request(L, "POST"); } LuaDefine(global_set, "varname, value", "|Store data in the global data table." "|" "|The variable name must be a string which is a valid" "|lua identifier." "|" "|You can store global data using global.set, then you can" "|retrieve it using global.get." "|" "|The global data table is not the same thing as the lua " "|environment table. Trying to store data in the lua environment" "|table will seem to work, at first, but the data will not get" "|difference transmitted, and will eventually be cleared and lost." "|Therefore, it is essential that global data be stored in the" "|global data table (using global.set) instead of in the lua" "|environment table." "|" "|Data is stored in a serialized form. The data is serialized" "|at the moment you call global.set. It is deserialized when you" "|call global.get. Therefore, each time you call global.get, you" "|construct another copy of the value." "|" "|The serialization routine can only serialize certain kinds of" "|values. For example, closures cannot be serialized at all." "|Serializing a tangible serializes the tangible's ID, but" "|none of the contents of the tangible. See doc(table.serialize)" "|for more information about what can and can't be serialized." "|") { LuaArg varname; LuaArg value; LuaDefStack LS(L, varname, value); // Check the varname argument. eng::string gvar = LS.ckstring(varname); if (!sv::is_lua_id(gvar)) { luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str()); return LS.result(); } // Serialize the data. StreamBuffer sb; eng::string error = serialize_lua(LS, value, &sb); if (!error.empty()) { luaL_error(L, "%s", error.c_str()); return LS.result(); } World *w = World::fetch_global_pointer(L); w->set_global(gvar, sb.view()); return LS.result(); } LuaDefine(global_get, "varname", "|Get data stored using global.set" "|" "|See doc(global.set) for information on how to store global data." "|" "|Performance note: each time you call global.get, you deserialize the" "|stored serialized version, constructing another copy of the data." "|") { LuaArg varname; LuaRet value; LuaDefStack LS(L, varname, value); LS.set(value, LuaNil); // Check the varname argument. eng::string gvar = LS.ckstring(varname); if (!sv::is_lua_id(gvar)) { luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str()); return LS.result(); } // Fetch the serialized blob. World *w = World::fetch_global_pointer(L); std::string_view data = w->get_global(gvar); if (data.empty()) return LS.result(); // Deserialize the blob. StreamBuffer sb(data); eng::string error = deserialize_lua(LS, value, &sb); if (!error.empty()) { luaL_error(L, "%s", error.c_str()); return LS.result(); } return LS.result(); } LuaDefine(global_once, "varname", "|For a given string, returns true exactly once" "|" "|The semantics and difference transmission behavior of global.once" "|are identical to the semantics of global.set, since global.once" "|uses global.set under the covers." "|") { LuaArg varname; LuaRet result; LuaDefStack LS(L, varname, result); LS.set(result, false); // Check the varname argument. eng::string gvar = LS.ckstring(varname); if (!sv::is_lua_id(gvar)) { luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str()); return LS.result(); } gvar += ":once"; World *w = World::fetch_global_pointer(L); LS.set(result, false); if (w->get_global(gvar).empty()) { LS.set(result, true); w->set_global(gvar, "x"); } return LS.result(); } LuaDefine(global_clearonce, "varname", "|Reset the specified once-flag" "|" "|The semantics and difference transmission behavior of global.clearonce" "|are identical to the semantics of global.set, since global.once" "|uses global.set under the covers." "|") { LuaArg varname; LuaDefStack LS(L, varname); // Check the varname argument. eng::string gvar = LS.ckstring(varname); if (!sv::is_lua_id(gvar)) { luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str()); return LS.result(); } gvar += ":once"; World *w = World::fetch_global_pointer(L); w->set_global(gvar, ""); return LS.result(); }