mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-28 01:43:22 +08:00
Bringing over more c++ stuff
This commit is contained in:
parent
c414c686a1
commit
64b008c304
@ -3934,14 +3934,14 @@
|
||||
"assets/build/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/50/8d/bc2600ac9491f1b14d659709451f",
|
||||
"build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ac/96/c3b9934061393fe09cc90ff24b8d",
|
||||
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/38/2b/5641b3b40846f74f232771ac0457",
|
||||
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/6e/7e/56adde97a5cb545933bdd52700d9",
|
||||
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/3c/f9/d971d471660647f1eacb768f0d10",
|
||||
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ef/b7/aa17c70752baab2bd4ea970b7b2d",
|
||||
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/43/77/27920088a7fb8490a833623894a1",
|
||||
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/70/3a/36ff319dbed727b6bd148073e278",
|
||||
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/83/81/5d46cb2627d0ae1f0c59a9dd123a",
|
||||
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/3a/8f/502e7fef458bb05da2864f4724ea",
|
||||
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/60/38/2d0e9f0cf486bae30056f3d3c11a",
|
||||
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/37/a6/ae4e2bf9c60fc0cbfd66136dc344",
|
||||
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/02/f4/907cfc73510e071f9ab5ca914646"
|
||||
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/6b/43/efceea678ab45ebe36c72ff6fa79",
|
||||
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/15/d5/29d9b25931f5c91a7a0db8cd6260",
|
||||
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/bc/bf/286df9a4a78d01c5bd02bee224cd",
|
||||
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/24/d5/6f25f7ffbdcf3dde835ca8213544",
|
||||
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/9c/d1/f745c2663299168c982f752802d0",
|
||||
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/9b/dd/90e274f18a93c82e9c2c29a59b41",
|
||||
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/9d/9a/c14692e42e5a7376b665af6a8463",
|
||||
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/2a/c8/f661b157edda3920f8834124d24b",
|
||||
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/7e/ed/5db67414f8d9444f91631a448bc0",
|
||||
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/7d/9f/9ff4e4c2d64c3dfac362f2b5af15"
|
||||
}
|
||||
14
.idea/dictionaries/ericf.xml
generated
14
.idea/dictionaries/ericf.xml
generated
@ -29,8 +29,8 @@
|
||||
<w>achname</w>
|
||||
<w>achs</w>
|
||||
<w>acinstance</w>
|
||||
<w>ack</w>
|
||||
<w>ack'ed</w>
|
||||
<w>ack</w>
|
||||
<w>acked</w>
|
||||
<w>acks</w>
|
||||
<w>acnt</w>
|
||||
@ -100,6 +100,7 @@
|
||||
<w>asdict</w>
|
||||
<w>aspx</w>
|
||||
<w>assertnode</w>
|
||||
<w>asserttype</w>
|
||||
<w>assetbundle</w>
|
||||
<w>assetcache</w>
|
||||
<w>assetdata</w>
|
||||
@ -151,8 +152,8 @@
|
||||
<w>bacommon</w>
|
||||
<w>badguy</w>
|
||||
<w>bafoundation</w>
|
||||
<w>ballistica</w>
|
||||
<w>ballistica's</w>
|
||||
<w>ballistica</w>
|
||||
<w>ballisticacore</w>
|
||||
<w>ballisticacorecb</w>
|
||||
<w>bamaster</w>
|
||||
@ -313,6 +314,7 @@
|
||||
<w>checkpaths</w>
|
||||
<w>checkroundover</w>
|
||||
<w>checksums</w>
|
||||
<w>checktype</w>
|
||||
<w>childnode</w>
|
||||
<w>chinesetraditional</w>
|
||||
<w>chipfork</w>
|
||||
@ -793,8 +795,8 @@
|
||||
<w>gamedata</w>
|
||||
<w>gameinstance</w>
|
||||
<w>gamemap</w>
|
||||
<w>gamepad</w>
|
||||
<w>gamepad's</w>
|
||||
<w>gamepad</w>
|
||||
<w>gamepadadvanced</w>
|
||||
<w>gamepads</w>
|
||||
<w>gamepadselect</w>
|
||||
@ -1177,8 +1179,8 @@
|
||||
<w>lsqlite</w>
|
||||
<w>lssl</w>
|
||||
<w>lstart</w>
|
||||
<w>lstr</w>
|
||||
<w>lstr's</w>
|
||||
<w>lstr</w>
|
||||
<w>lstrs</w>
|
||||
<w>lsval</w>
|
||||
<w>ltex</w>
|
||||
@ -1803,8 +1805,8 @@
|
||||
<w>sessionname</w>
|
||||
<w>sessionplayer</w>
|
||||
<w>sessionplayers</w>
|
||||
<w>sessionteam</w>
|
||||
<w>sessionteam's</w>
|
||||
<w>sessionteam</w>
|
||||
<w>sessionteams</w>
|
||||
<w>sessiontype</w>
|
||||
<w>setactivity</w>
|
||||
@ -2135,8 +2137,8 @@
|
||||
<w>txtw</w>
|
||||
<w>typeargs</w>
|
||||
<w>typecheck</w>
|
||||
<w>typechecker</w>
|
||||
<w>typechecker's</w>
|
||||
<w>typechecker</w>
|
||||
<w>typedval</w>
|
||||
<w>typeshed</w>
|
||||
<w>typestr</w>
|
||||
|
||||
@ -67,6 +67,7 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
|
||||
def _show_standard_scores_to_beat_ui(self,
|
||||
scores: List[Dict[str, Any]]) -> None:
|
||||
from efro.util import asserttype
|
||||
from ba._gameutils import timestring, animate
|
||||
from ba._nodeactor import NodeActor
|
||||
from ba._enums import TimeFormat
|
||||
@ -74,7 +75,7 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
if scores is not None:
|
||||
|
||||
# Sort by originating date so that the most recent is first.
|
||||
scores.sort(reverse=True, key=lambda s: s['time'])
|
||||
scores.sort(reverse=True, key=lambda s: asserttype(s['time'], int))
|
||||
|
||||
# Now make a display for the most recent challenge.
|
||||
for score in scores:
|
||||
|
||||
@ -53,6 +53,7 @@ class FreeForAllSession(MultiTeamSession):
|
||||
|
||||
def _switch_to_score_screen(self, results: ba.GameResults) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from efro.util import asserttype
|
||||
from bastd.activity.drawscore import DrawScoreScreenActivity
|
||||
from bastd.activity.multiteamvictory import (
|
||||
TeamSeriesVictoryScoreScreenActivity)
|
||||
@ -80,8 +81,9 @@ class FreeForAllSession(MultiTeamSession):
|
||||
team for team in self.sessionteams
|
||||
if team.customdata['score'] >= self._ffa_series_length
|
||||
]
|
||||
series_winners.sort(reverse=True,
|
||||
key=lambda tm: (tm.customdata['score']))
|
||||
series_winners.sort(
|
||||
reverse=True,
|
||||
key=lambda t: asserttype(t.customdata['score'], int))
|
||||
if (len(series_winners) == 1
|
||||
or (len(series_winners) > 1
|
||||
and series_winners[0].customdata['score'] !=
|
||||
|
||||
@ -8,6 +8,7 @@ import weakref
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from efro.util import asserttype
|
||||
from ba._team import Team, SessionTeam
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -187,7 +188,8 @@ class GameResults:
|
||||
sval.append(team)
|
||||
results: List[Tuple[Optional[int],
|
||||
List[ba.SessionTeam]]] = list(winners.items())
|
||||
results.sort(reverse=not self._lower_is_better, key=lambda x: x[0])
|
||||
results.sort(reverse=not self._lower_is_better,
|
||||
key=lambda x: asserttype(x[0], int))
|
||||
|
||||
# Also group the 'None' scores.
|
||||
none_sessionteams: List[ba.SessionTeam] = []
|
||||
|
||||
@ -328,11 +328,6 @@ def local_chat_message(msg: str) -> None:
|
||||
_ba.app.ui.party_window().on_chat_message(msg)
|
||||
|
||||
|
||||
def handle_remote_achievement_list(completed_achievements: List[str]) -> None:
|
||||
from ba import _achievement
|
||||
_achievement.set_completed_achievements(completed_achievements)
|
||||
|
||||
|
||||
def get_player_icon(sessionplayer: ba.SessionPlayer) -> Dict[str, Any]:
|
||||
info = sessionplayer.get_icon_info()
|
||||
return {
|
||||
|
||||
@ -500,8 +500,9 @@ def get_available_sale_time(tab: str) -> Optional[int]:
|
||||
if to_end > 0:
|
||||
sale_times.append(int(to_end * 1000))
|
||||
|
||||
# Return the smallest time i guess?
|
||||
return min(sale_times) if sale_times else None
|
||||
# Return the smallest time I guess?
|
||||
sale_times_int = [t for t in sale_times if isinstance(t, int)]
|
||||
return min(sale_times_int) if sale_times_int else None
|
||||
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
@ -57,12 +57,14 @@ class CoopJoinActivity(JoinActivity):
|
||||
scores: Optional[List[Dict[str, Any]]]) -> None:
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-statements
|
||||
from efro.util import asserttype
|
||||
from bastd.actor.text import Text
|
||||
from ba.internal import get_achievements_for_coop_level
|
||||
|
||||
# Sort by originating date so that the most recent is first.
|
||||
if scores is not None:
|
||||
scores.sort(reverse=True, key=lambda score: score['time'])
|
||||
scores.sort(reverse=True,
|
||||
key=lambda score: asserttype(score['time'], int))
|
||||
|
||||
# We only show achievements and challenges for CoopGameActivities.
|
||||
session = self.session
|
||||
|
||||
@ -873,6 +873,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-statements
|
||||
from efro.util import asserttype
|
||||
# delay a bit if results come in too fast
|
||||
assert self._begin_time is not None
|
||||
base_delay = max(0, 1.9 - (ba.time() - self._begin_time))
|
||||
@ -909,7 +910,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
|
||||
break
|
||||
results.append(our_score_entry)
|
||||
results.sort(reverse=self._score_order == 'increasing',
|
||||
key=lambda x: x[0])
|
||||
key=lambda x: asserttype(x[0], int))
|
||||
|
||||
# If we're not submitting our own score, we still want to change the
|
||||
# name of our own score to 'Me'.
|
||||
|
||||
@ -284,6 +284,7 @@ class PlaylistBrowserWindow(ba.Window):
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-nested-blocks
|
||||
from efro.util import asserttype
|
||||
from ba.internal import (get_map_class,
|
||||
get_default_free_for_all_playlist,
|
||||
get_default_teams_playlist, filter_playlist)
|
||||
@ -303,7 +304,7 @@ class PlaylistBrowserWindow(ba.Window):
|
||||
items = [(i[0].decode(), i[1]) if not isinstance(i[0], str) else i
|
||||
for i in items]
|
||||
|
||||
items.sort(key=lambda x2: x2[0].lower())
|
||||
items.sort(key=lambda x2: asserttype(x2[0], str).lower())
|
||||
items = [['__default__', None]] + items # default is always first
|
||||
|
||||
count = len(items)
|
||||
|
||||
@ -299,6 +299,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
|
||||
_ba.lock_all_input()
|
||||
|
||||
def _refresh(self, select_playlist: str = None) -> None:
|
||||
from efro.util import asserttype
|
||||
old_selection = self._selected_playlist_name
|
||||
|
||||
# If there was no prev selection, look in prefs.
|
||||
@ -318,7 +319,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
|
||||
items = [(i[0].decode(), i[1]) if not isinstance(i[0], str) else i
|
||||
for i in items]
|
||||
|
||||
items.sort(key=lambda x: x[0].lower())
|
||||
items.sort(key=lambda x: asserttype(x[0], str).lower())
|
||||
|
||||
items = [['__default__', None]] + items # Default is always first.
|
||||
index = 0
|
||||
|
||||
@ -270,6 +270,7 @@ class ProfileBrowserWindow(ba.Window):
|
||||
|
||||
def _refresh(self) -> None:
|
||||
# pylint: disable=too-many-locals
|
||||
from efro.util import asserttype
|
||||
from ba.internal import (PlayerProfilesChangedMessage,
|
||||
get_player_profile_colors,
|
||||
get_player_profile_icon)
|
||||
@ -281,7 +282,7 @@ class ProfileBrowserWindow(ba.Window):
|
||||
self._profiles = ba.app.config.get('Player Profiles', {})
|
||||
assert self._profiles is not None
|
||||
items = list(self._profiles.items())
|
||||
items.sort(key=lambda x: x[0].lower())
|
||||
items.sort(key=lambda x: asserttype(x[0], str).lower())
|
||||
index = 0
|
||||
account_name: Optional[str]
|
||||
if _ba.get_account_state() == 'signed_in':
|
||||
|
||||
@ -362,6 +362,7 @@ class SoundtrackBrowserWindow(ba.Window):
|
||||
return ba.Lstr(value=soundtrack)
|
||||
|
||||
def _refresh(self, select_soundtrack: str = None) -> None:
|
||||
from efro.util import asserttype
|
||||
self._allow_changing_soundtracks = False
|
||||
old_selection = self._selected_soundtrack
|
||||
|
||||
@ -377,7 +378,7 @@ class SoundtrackBrowserWindow(ba.Window):
|
||||
self._soundtracks = ba.app.config.get('Soundtracks', {})
|
||||
assert self._soundtracks is not None
|
||||
items = list(self._soundtracks.items())
|
||||
items.sort(key=lambda x: x[0].lower())
|
||||
items.sort(key=lambda x: asserttype(x[0], str).lower())
|
||||
items = [('__default__', None)] + items # default is always first
|
||||
index = 0
|
||||
for pname, _pval in items:
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
|
||||
<h4><em>last updated on 2020-10-09 for Ballistica version 1.5.26 build 20195</em></h4>
|
||||
<h4><em>last updated on 2020-10-09 for Ballistica version 1.5.26 build 20198</em></h4>
|
||||
<p>This page documents the Python classes and functions in the 'ba' module,
|
||||
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p>
|
||||
<hr>
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
namespace ballistica {
|
||||
|
||||
// These are set automatically via script; don't change here.
|
||||
const int kAppBuildNumber = 20196;
|
||||
const int kAppBuildNumber = 20198;
|
||||
const char* kAppVersion = "1.5.26";
|
||||
|
||||
// Our standalone globals.
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
@ -151,10 +152,32 @@ auto GetUniqueSessionIdentifier() -> const std::string&;
|
||||
/// Have our main threads/modules all been inited yet?
|
||||
auto IsBootstrapped() -> bool;
|
||||
|
||||
/// Create/init our internal (non-public) parts.
|
||||
/// Internal bits.
|
||||
auto CreateAppInternal() -> AppInternal*;
|
||||
auto AppInternalGameThreadInit() -> void;
|
||||
auto AppInternalPythonInit() -> PyObject*;
|
||||
auto AppInternalPythonInit2() -> void;
|
||||
auto AppInternalHasBlessingHash() -> bool;
|
||||
auto AppInternalPutLog(bool fatal) -> bool;
|
||||
auto AppInternalAwardAdTickets() -> void;
|
||||
auto AppInternalAwardAdTournamentEntry() -> void;
|
||||
auto AppInternalSetAdCompletionCall(PyObject* obj, bool pass_actually_showed)
|
||||
-> void;
|
||||
auto AppInternalPushAdViewComplete(const std::string& purpose,
|
||||
bool actually_showed) -> void;
|
||||
auto AppInternalPushPublicPartyState() -> void;
|
||||
auto AppInternalPushSetFriendListCall(const std::vector<std::string>& friends)
|
||||
-> void;
|
||||
auto AppInternalDispatchRemoteAchievementList(const std::set<std::string>& achs)
|
||||
-> void;
|
||||
auto AppInternalPushAnalyticsCall(const std::string& type, int increment)
|
||||
-> void;
|
||||
auto AppInternalPushPurchaseTransactionCall(const std::string& item,
|
||||
const std::string& receipt,
|
||||
const std::string& signature,
|
||||
const std::string& order_id,
|
||||
bool user_initiated) -> void;
|
||||
auto AppInternalGetPublicAccountID() -> std::string;
|
||||
auto AppInternalOnGameThreadPause() -> void;
|
||||
|
||||
/// Does it appear that we are a blessed build with no known user-modifications?
|
||||
auto IsUnmodifiedBlessedBuild() -> bool;
|
||||
|
||||
@ -36,16 +36,8 @@ class Game : public Module {
|
||||
const std::string& account_id) -> void;
|
||||
auto PushSetAccountTokenCall(const std::string& account_id,
|
||||
const std::string& token) -> void;
|
||||
auto PushAdViewCompleteCall(const std::string& purpose, bool actually_showed)
|
||||
-> void;
|
||||
auto PushAnalyticsCall(const std::string& type, int increment) -> void;
|
||||
auto PushAwardAdTicketsCall() -> void;
|
||||
auto PushAwardAdTournamentEntryCall() -> void;
|
||||
auto PushPurchaseTransactionCall(const std::string& item,
|
||||
const std::string& receipt,
|
||||
const std::string& signature,
|
||||
const std::string& order_id,
|
||||
bool user_initiated) -> void;
|
||||
auto PushUDPConnectionPacketCall(const std::vector<uint8_t>& data,
|
||||
const SockAddr& addr) -> void;
|
||||
auto PushPartyInviteCall(const std::string& name,
|
||||
@ -78,7 +70,6 @@ class Game : public Module {
|
||||
auto PushHavePendingLoadsDoneCall() -> void;
|
||||
auto PushFreeMediaComponentRefsCall(
|
||||
const std::vector<Object::Ref<MediaComponentData>*>& components) -> void;
|
||||
auto PushSetFriendListCall(const std::vector<std::string>& friends) -> void;
|
||||
auto PushHavePendingLoadsCall() -> void;
|
||||
auto PushShutdownCall(bool soft) -> void;
|
||||
|
||||
@ -289,10 +280,6 @@ class Game : public Module {
|
||||
auto LocalDisplayChatMessage(const std::vector<uint8_t>& buffer) -> void;
|
||||
auto ShouldAnnouncePartyJoinsAndLeaves() -> bool;
|
||||
|
||||
auto SetAdCompletionCall(PyObject* obj, bool pass_actually_showed) -> void;
|
||||
auto CallAdCompletionCall(bool actually_showed) -> void;
|
||||
auto RunGeneralAdComplete(bool actually_watched) -> void;
|
||||
|
||||
auto StartKickVote(ConnectionToClient* starter, ConnectionToClient* target)
|
||||
-> void;
|
||||
auto require_client_authentication() const {
|
||||
@ -330,6 +317,15 @@ class Game : public Module {
|
||||
auto public_party_size() const { return public_party_size_; }
|
||||
auto SetPublicPartySize(int count) -> void;
|
||||
auto public_party_max_size() const { return public_party_max_size_; }
|
||||
auto public_party_max_player_count() const {
|
||||
return public_party_max_player_count_;
|
||||
}
|
||||
auto public_party_min_league() const -> const std::string& {
|
||||
return public_party_min_league_;
|
||||
}
|
||||
auto public_party_stats_url() const -> const std::string& {
|
||||
return public_party_stats_url_;
|
||||
}
|
||||
auto SetPublicPartyMaxSize(int count) -> void;
|
||||
auto SetPublicPartyName(const std::string& name) -> void;
|
||||
auto SetPublicPartyStatsURL(const std::string& name) -> void;
|
||||
@ -341,14 +337,9 @@ class Game : public Module {
|
||||
private:
|
||||
auto InitSpecialChars() -> void;
|
||||
auto AdViewComplete(const std::string& purpose, bool actually_showed) -> void;
|
||||
auto Analytics(const std::string& type, int increment) -> void;
|
||||
auto AwardAdTickets() -> void;
|
||||
auto AwardAdTournamentEntry() -> void;
|
||||
auto Draw() -> void;
|
||||
auto PurchaseTransaction(const std::string& item, const std::string& receipt,
|
||||
const std::string& signature,
|
||||
const std::string& order_id, bool user_initiated)
|
||||
-> void;
|
||||
auto UDPConnectionPacket(const std::vector<uint8_t>& data,
|
||||
const SockAddr& addr) -> void;
|
||||
auto PartyInvite(const std::string& name, const std::string& invite_id)
|
||||
@ -384,7 +375,6 @@ class Game : public Module {
|
||||
auto GetGameRosterMessage() -> std::vector<uint8_t>;
|
||||
auto CleanUpBeforeConnectingToHost() -> void;
|
||||
auto Shutdown(bool soft) -> void;
|
||||
auto PushPublicPartyState() -> void;
|
||||
|
||||
std::map<int, int> google_play_id_to_client_id_map_;
|
||||
std::map<int, int> client_id_to_google_play_id_map_;
|
||||
@ -446,9 +436,6 @@ class Game : public Module {
|
||||
int last_kick_votes_needed_{-1};
|
||||
Object::WeakRef<ConnectionToClient> kick_vote_starter_;
|
||||
Object::WeakRef<ConnectionToClient> kick_vote_target_;
|
||||
Object::Ref<PythonContextCall> ad_completion_callback_;
|
||||
millisecs_t last_ad_start_time_{};
|
||||
bool ad_completion_callback_pass_actually_showed_{};
|
||||
bool public_party_enabled_{false};
|
||||
int public_party_size_{1}; // Always count ourself (is that what we want?).
|
||||
int public_party_max_size_{8};
|
||||
|
||||
@ -461,130 +461,6 @@ void Utils::SetRandomNameList(const std::list<std::string>& custom_names) {
|
||||
}
|
||||
}
|
||||
|
||||
#define HEXVAL(x) ('0' + (x) + ((x) > 9u) * 7u)
|
||||
static auto ToHex(const std::string& s_in) -> std::string {
|
||||
uint32_t s_size = static_cast<int>(s_in.size());
|
||||
std::string s_out;
|
||||
s_out.resize(static_cast<size_t>(s_size) * 2);
|
||||
for (uint32_t i = 0; i < s_size; i++) {
|
||||
s_out[i * 2] =
|
||||
static_cast<char>(HEXVAL((static_cast<uint32_t>(s_in[i])) >> 4u));
|
||||
s_out[i * 2 + 1] =
|
||||
static_cast<char>(HEXVAL((static_cast<uint32_t>(s_in[i]) & 15u)));
|
||||
}
|
||||
return s_out;
|
||||
}
|
||||
#undef HEXVAL
|
||||
|
||||
static auto FromHex(const std::string& s_in) -> std::string {
|
||||
int s_size = static_cast<int>(s_in.size());
|
||||
BA_PRECONDITION(s_size % 2 == 0);
|
||||
s_size /= 2;
|
||||
std::string s_out;
|
||||
s_out.resize(static_cast<size_t>(s_size));
|
||||
for (int i = 0; i < s_size; i++) {
|
||||
auto val = (uint32_t)s_in[i * 2]; // NOLINT(cert-str34-c)
|
||||
if (val >= '0' && val <= '9') {
|
||||
s_out[i] = static_cast<char>((val - '0') << 4u);
|
||||
} else if (val >= 'A' && val <= 'F') {
|
||||
s_out[i] = static_cast<char>((10u + (val - 'A')) << 4u);
|
||||
} else {
|
||||
throw Exception();
|
||||
}
|
||||
val = (uint32_t)s_in[i * 2 + 1]; // NOLINT(cert-str34-c)
|
||||
if (val >= '0' && val <= '9') {
|
||||
s_out[i] =
|
||||
static_cast<char>(static_cast<uint32_t>(s_out[i]) | (val - '0'));
|
||||
} else if (val >= 'A' && val <= 'F') {
|
||||
s_out[i] = static_cast<char>(static_cast<uint32_t>(s_out[i])
|
||||
| (10 + (val - 'A')));
|
||||
} else {
|
||||
throw Exception();
|
||||
}
|
||||
}
|
||||
return s_out;
|
||||
}
|
||||
|
||||
static auto EncryptDecrypt(const std::string& to_encrypt) -> std::string {
|
||||
assert(g_platform);
|
||||
const char* key = g_platform->GetUniqueDeviceIdentifier().c_str();
|
||||
int key_size =
|
||||
static_cast<int>(g_platform->GetUniqueDeviceIdentifier().size());
|
||||
std::string output = to_encrypt;
|
||||
for (size_t i = 0; i < to_encrypt.size(); i++) {
|
||||
output[i] = to_encrypt[i] ^ key[i % (key_size)]; // NOLINT
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
static auto EncryptDecryptCustom(const std::string& to_encrypt,
|
||||
const std::string& key_in) -> std::string {
|
||||
assert(g_platform);
|
||||
const char* key = key_in.c_str();
|
||||
int key_size = static_cast<int>(key_in.size());
|
||||
std::string output = to_encrypt;
|
||||
for (size_t i = 0; i < to_encrypt.size(); i++) {
|
||||
output[i] = to_encrypt[i] ^ key[i % (key_size)]; // NOLINT
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
static auto PublicEncryptDecrypt(const std::string& to_encrypt) -> std::string {
|
||||
std::string key_str = "create an account"; // A non-key-looking key.
|
||||
const char* key = key_str.c_str();
|
||||
int key_size = static_cast<int>(key_str.size());
|
||||
std::string output = to_encrypt;
|
||||
for (size_t i = 0; i < to_encrypt.size(); i++)
|
||||
output[i] = to_encrypt[i] ^ key[i % (key_size)]; // NOLINT
|
||||
return output;
|
||||
}
|
||||
|
||||
auto Utils::LocalEncrypt(const std::string& s_in) -> std::string {
|
||||
return ToHex(EncryptDecrypt(s_in));
|
||||
}
|
||||
|
||||
auto Utils::LocalEncrypt2(const std::string& s_in) -> std::string {
|
||||
std::string s = EncryptDecrypt(s_in);
|
||||
return base64_encode((const unsigned char*)s.c_str(),
|
||||
static_cast<int>(s.size()));
|
||||
}
|
||||
auto Utils::EncryptCustom(const std::string& s_in, const std::string& key)
|
||||
-> std::string {
|
||||
std::string s = EncryptDecryptCustom(s_in, key);
|
||||
return base64_encode((const unsigned char*)s.c_str(),
|
||||
static_cast<int>(s.size()));
|
||||
}
|
||||
|
||||
auto Utils::LocalDecrypt(const std::string& s_in) -> std::string {
|
||||
return EncryptDecrypt(FromHex(s_in));
|
||||
}
|
||||
|
||||
auto Utils::LocalDecrypt2(const std::string& s_in) -> std::string {
|
||||
return EncryptDecrypt(base64_decode(s_in));
|
||||
}
|
||||
auto Utils::DecryptCustom(const std::string& s_in, const std::string& key)
|
||||
-> std::string {
|
||||
return EncryptDecryptCustom(base64_decode(s_in), key);
|
||||
}
|
||||
|
||||
auto Utils::PublicEncrypt(const std::string& s_in) -> std::string {
|
||||
return ToHex(PublicEncryptDecrypt(s_in));
|
||||
}
|
||||
|
||||
auto Utils::PublicDecrypt(const std::string& s_in) -> std::string {
|
||||
return PublicEncryptDecrypt(FromHex(s_in));
|
||||
}
|
||||
|
||||
auto Utils::PublicEncrypt2(const std::string& s_in) -> std::string {
|
||||
std::string s = PublicEncryptDecrypt(s_in);
|
||||
return base64_encode((const unsigned char*)s.c_str(),
|
||||
static_cast<int>(s.size()));
|
||||
}
|
||||
|
||||
auto Utils::PublicDecrypt2(const std::string& s_in) -> std::string {
|
||||
return PublicEncryptDecrypt(base64_decode(s_in));
|
||||
}
|
||||
|
||||
auto Utils::Sphrand(float radius) -> Vector3f {
|
||||
while (true) {
|
||||
float x = RandomFloat();
|
||||
|
||||
@ -343,28 +343,6 @@ class Utils {
|
||||
static float precalc_rands_3[];
|
||||
auto huffman() -> Huffman* { return huffman_.get(); }
|
||||
|
||||
/// Encrypt a string in a manner specific to this device.
|
||||
static auto LocalEncrypt(const std::string& s) -> std::string;
|
||||
static auto LocalEncrypt2(const std::string& s) -> std::string;
|
||||
|
||||
/// Decode a local string that was encoded specific to this device.
|
||||
/// Throws an exception on failure.
|
||||
static auto LocalDecrypt(const std::string& s) -> std::string;
|
||||
static auto LocalDecrypt2(const std::string& s) -> std::string;
|
||||
|
||||
/// Encrypt a string using a custom key.
|
||||
static auto EncryptCustom(const std::string& s, const std::string& key)
|
||||
-> std::string;
|
||||
/// Decrypt a string using a custom key.
|
||||
static auto DecryptCustom(const std::string& s, const std::string& key)
|
||||
-> std::string;
|
||||
|
||||
/// Encrypt/decrypt strings to send to the master-server
|
||||
static auto PublicEncrypt(const std::string& s) -> std::string;
|
||||
static auto PublicDecrypt(const std::string& s) -> std::string;
|
||||
static auto PublicEncrypt2(const std::string& s) -> std::string;
|
||||
static auto PublicDecrypt2(const std::string& s) -> std::string;
|
||||
|
||||
// FIXME - move to a nice math-y place
|
||||
static auto Sphrand(float radius = 1.0f) -> Vector3f;
|
||||
|
||||
|
||||
@ -78,14 +78,10 @@ auto InputDevice::GetPlayerProfiles() const -> PyObject* { return nullptr; }
|
||||
auto InputDevice::GetPublicAccountID() const -> std::string {
|
||||
assert(InGameThread());
|
||||
|
||||
// this default implementation assumes the device is local
|
||||
// so just returns the locally signed in account's public id..
|
||||
// This default implementation assumes the device is local
|
||||
// so just returns the locally signed in account's public id.
|
||||
|
||||
// the master-server makes our public account-id available to us
|
||||
// through a misc-read-val; look for that..
|
||||
std::string pub_id =
|
||||
g_python->GetAccountMiscReadVal2String("resolvedAccountID");
|
||||
return pub_id;
|
||||
return AppInternalGetPublicAccountID();
|
||||
}
|
||||
|
||||
auto InputDevice::GetAccountName(bool full) const -> std::string {
|
||||
|
||||
69
src/ballistica/python/class/python_class.cc
Normal file
69
src/ballistica/python/class/python_class.cc
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/class/python_class.h"
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
void PythonClass::SetupType(PyTypeObject* obj) {
|
||||
PyTypeObject t = {
|
||||
PyVarObject_HEAD_INIT(nullptr, 0)
|
||||
// .tp_name = "ba.Object",
|
||||
// .tp_basicsize = sizeof(PythonClass),
|
||||
// .tp_itemsize = 0,
|
||||
// .tp_dealloc = (destructor)tp_dealloc,
|
||||
// .tp_repr = (reprfunc)tp_repr,
|
||||
// .tp_getattro = (getattrofunc)tp_getattro,
|
||||
// .tp_setattro = (setattrofunc)tp_setattro,
|
||||
// .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
||||
// .tp_doc = "A ballistica object.",
|
||||
// .tp_new = tp_new,
|
||||
};
|
||||
|
||||
// python samples use the initializer style above, but it fails
|
||||
// in g++ and sounds like it might not be allowed in c++ anyway,
|
||||
// ..so this is close enough...
|
||||
// (and still more readable than setting ALL values positionally)
|
||||
assert(t.tp_itemsize == 0); // should all be zeroed though..
|
||||
t.tp_name = "ba.Object";
|
||||
t.tp_basicsize = sizeof(PythonClass);
|
||||
t.tp_itemsize = 0;
|
||||
t.tp_dealloc = (destructor)tp_dealloc;
|
||||
// t.tp_repr = (reprfunc)tp_repr;
|
||||
t.tp_getattro = (getattrofunc)tp_getattro;
|
||||
t.tp_setattro = (setattrofunc)tp_setattro;
|
||||
// NOLINTNEXTLINE (signed bitwise ops)
|
||||
t.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
|
||||
t.tp_doc = "A ballistica object.";
|
||||
t.tp_new = tp_new;
|
||||
|
||||
memcpy(obj, &t, sizeof(t));
|
||||
}
|
||||
|
||||
auto PythonClass::tp_repr(PythonClass* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
return Py_BuildValue("s", "<Ballistica Object>");
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
auto PythonClass::tp_new(PyTypeObject* type, PyObject* args, PyObject* kwds)
|
||||
-> PyObject* {
|
||||
auto* self = reinterpret_cast<PythonClass*>(type->tp_alloc(type, 0));
|
||||
return reinterpret_cast<PyObject*>(self);
|
||||
}
|
||||
void PythonClass::tp_dealloc(PythonClass* self) {
|
||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
||||
}
|
||||
auto PythonClass::tp_getattro(PythonClass* node, PyObject* attr) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
return PyObject_GenericGetAttr(reinterpret_cast<PyObject*>(node), attr);
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
auto PythonClass::tp_setattro(PythonClass* node, PyObject* attr, PyObject* val)
|
||||
-> int {
|
||||
BA_PYTHON_TRY;
|
||||
return PyObject_GenericSetAttr(reinterpret_cast<PyObject*>(node), attr, val);
|
||||
BA_PYTHON_INT_CATCH;
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
28
src/ballistica/python/class/python_class.h
Normal file
28
src/ballistica/python/class/python_class.h
Normal file
@ -0,0 +1,28 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_H_
|
||||
#define BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_H_
|
||||
|
||||
#include "ballistica/python/python_sys.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// a convenient base class for defining custom python types
|
||||
class PythonClass {
|
||||
public:
|
||||
PyObject_HEAD;
|
||||
static void SetupType(PyTypeObject* obj);
|
||||
|
||||
private:
|
||||
static auto tp_repr(PythonClass* self) -> PyObject*;
|
||||
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* kwds)
|
||||
-> PyObject*;
|
||||
static void tp_dealloc(PythonClass* self);
|
||||
static auto tp_getattro(PythonClass* node, PyObject* attr) -> PyObject*;
|
||||
static auto tp_setattro(PythonClass* node, PyObject* attr, PyObject* val)
|
||||
-> int;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_H_
|
||||
183
src/ballistica/python/class/python_class_activity_data.cc
Normal file
183
src/ballistica/python/class/python_class_activity_data.cc
Normal file
@ -0,0 +1,183 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/class/python_class_activity_data.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/game/game.h"
|
||||
#include "ballistica/game/host_activity.h"
|
||||
#include "ballistica/game/session/host_session.h"
|
||||
#include "ballistica/generic/utils.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
auto PythonClassActivityData::nb_bool(PythonClassActivityData* self) -> int {
|
||||
return self->host_activity_->exists();
|
||||
}
|
||||
|
||||
PyNumberMethods PythonClassActivityData::as_number_;
|
||||
|
||||
void PythonClassActivityData::SetupType(PyTypeObject* obj) {
|
||||
PythonClass::SetupType(obj);
|
||||
obj->tp_name = "_ba.ActivityData";
|
||||
obj->tp_basicsize = sizeof(PythonClassActivityData);
|
||||
obj->tp_doc = "(internal)";
|
||||
obj->tp_new = tp_new;
|
||||
obj->tp_dealloc = (destructor)tp_dealloc;
|
||||
obj->tp_repr = (reprfunc)tp_repr;
|
||||
obj->tp_methods = tp_methods;
|
||||
|
||||
// We provide number methods only for bool functionality.
|
||||
memset(&as_number_, 0, sizeof(as_number_));
|
||||
as_number_.nb_bool = (inquiry)nb_bool;
|
||||
obj->tp_as_number = &as_number_;
|
||||
}
|
||||
|
||||
auto PythonClassActivityData::Create(HostActivity* host_activity) -> PyObject* {
|
||||
auto* py_activity_data = reinterpret_cast<PythonClassActivityData*>(
|
||||
PyObject_CallObject(reinterpret_cast<PyObject*>(&type_obj), nullptr));
|
||||
BA_PRECONDITION(py_activity_data);
|
||||
*(py_activity_data->host_activity_) = host_activity;
|
||||
return reinterpret_cast<PyObject*>(py_activity_data);
|
||||
}
|
||||
|
||||
auto PythonClassActivityData::GetHostActivity() const -> HostActivity* {
|
||||
HostActivity* host_activity = host_activity_->get();
|
||||
if (!host_activity)
|
||||
throw Exception(
|
||||
"Invalid ActivityData; this activity has probably been expired and "
|
||||
"should not be getting used.");
|
||||
return host_activity;
|
||||
}
|
||||
|
||||
auto PythonClassActivityData::tp_repr(PythonClassActivityData* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
return Py_BuildValue(
|
||||
"s", (std::string("<Ballistica ActivityData ")
|
||||
+ Utils::PtrToString(self->host_activity_->get()) + " >")
|
||||
.c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassActivityData::tp_new(PyTypeObject* type, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
auto* self =
|
||||
reinterpret_cast<PythonClassActivityData*>(type->tp_alloc(type, 0));
|
||||
if (self) {
|
||||
BA_PYTHON_TRY;
|
||||
if (!InGameThread()) {
|
||||
throw Exception(
|
||||
"ERROR: " + std::string(type_obj.tp_name)
|
||||
+ " objects must only be created in the game thread (current is ("
|
||||
+ GetCurrentThreadName() + ").");
|
||||
}
|
||||
self->host_activity_ = new Object::WeakRef<HostActivity>();
|
||||
BA_PYTHON_NEW_CATCH;
|
||||
}
|
||||
return reinterpret_cast<PyObject*>(self);
|
||||
}
|
||||
|
||||
void PythonClassActivityData::tp_dealloc(PythonClassActivityData* self) {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
// These have to be destructed in the game thread; send them along to
|
||||
// it if need be; otherwise do it immediately.
|
||||
if (!InGameThread()) {
|
||||
Object::WeakRef<HostActivity>* h = self->host_activity_;
|
||||
g_game->PushCall([h] { delete h; });
|
||||
} else {
|
||||
delete self->host_activity_;
|
||||
}
|
||||
BA_PYTHON_DEALLOC_CATCH;
|
||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
||||
}
|
||||
|
||||
auto PythonClassActivityData::exists(PythonClassActivityData* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
HostActivity* host_activity = self->host_activity_->get();
|
||||
if (host_activity) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassActivityData::make_foreground(PythonClassActivityData* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
HostActivity* a = self->host_activity_->get();
|
||||
if (!a) {
|
||||
throw Exception("Invalid activity.", PyExcType::kActivityNotFound);
|
||||
}
|
||||
HostSession* session = a->GetHostSession();
|
||||
if (!session) {
|
||||
throw Exception("Activity's Session not found.",
|
||||
PyExcType::kSessionNotFound);
|
||||
}
|
||||
session->SetForegroundHostActivity(a);
|
||||
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassActivityData::start(PythonClassActivityData* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
HostActivity* a = self->host_activity_->get();
|
||||
if (!a) {
|
||||
throw Exception("Invalid activity data.", PyExcType::kActivityNotFound);
|
||||
}
|
||||
a->start();
|
||||
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassActivityData::expire(PythonClassActivityData* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
HostActivity* a = self->host_activity_->get();
|
||||
|
||||
// The python side may have stuck around after our c++ side was
|
||||
// torn down; that's ok.
|
||||
if (a) {
|
||||
HostSession* session = a->GetHostSession();
|
||||
if (!session) {
|
||||
throw Exception("Activity's Session not found.",
|
||||
PyExcType::kSessionNotFound);
|
||||
}
|
||||
session->DestroyHostActivity(a);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
PyTypeObject PythonClassActivityData::type_obj;
|
||||
PyMethodDef PythonClassActivityData::tp_methods[] = {
|
||||
{"exists", (PyCFunction)exists, METH_NOARGS,
|
||||
"exists() -> bool\n"
|
||||
"\n"
|
||||
"Returns whether the ActivityData still exists.\n"
|
||||
"Most functionality will fail on a nonexistent instance."},
|
||||
{"make_foreground", (PyCFunction)make_foreground, METH_NOARGS,
|
||||
"make_foreground() -> None\n"
|
||||
"\n"
|
||||
"Sets this activity as the foreground one in its session."},
|
||||
{"expire", (PyCFunction)expire, METH_NOARGS,
|
||||
"expire() -> None\n"
|
||||
"\n"
|
||||
"Expires the internal data for the activity"},
|
||||
{"start", (PyCFunction)start, METH_NOARGS,
|
||||
"start() -> None\n"
|
||||
"\n"
|
||||
"Begins the activity running"},
|
||||
{nullptr}};
|
||||
|
||||
} // namespace ballistica
|
||||
39
src/ballistica/python/class/python_class_activity_data.h
Normal file
39
src/ballistica/python/class/python_class_activity_data.h
Normal file
@ -0,0 +1,39 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_ACTIVITY_DATA_H_
|
||||
#define BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_ACTIVITY_DATA_H_
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/python/class/python_class.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class PythonClassActivityData : public PythonClass {
|
||||
public:
|
||||
static auto type_name() -> const char* { return "ActivityData"; }
|
||||
static void SetupType(PyTypeObject* obj);
|
||||
static auto Create(HostActivity* host_activity) -> PyObject*;
|
||||
static auto Check(PyObject* o) -> bool {
|
||||
return PyObject_TypeCheck(o, &type_obj);
|
||||
}
|
||||
static PyTypeObject type_obj;
|
||||
auto GetHostActivity() const -> HostActivity*;
|
||||
|
||||
private:
|
||||
static PyMethodDef tp_methods[];
|
||||
static auto tp_repr(PythonClassActivityData* self) -> PyObject*;
|
||||
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds)
|
||||
-> PyObject*;
|
||||
static void tp_dealloc(PythonClassActivityData* self);
|
||||
static auto exists(PythonClassActivityData* self) -> PyObject*;
|
||||
static auto make_foreground(PythonClassActivityData* self) -> PyObject*;
|
||||
static auto start(PythonClassActivityData* self) -> PyObject*;
|
||||
static auto expire(PythonClassActivityData* self) -> PyObject*;
|
||||
Object::WeakRef<HostActivity>* host_activity_;
|
||||
static auto nb_bool(PythonClassActivityData* self) -> int;
|
||||
static PyNumberMethods as_number_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_ACTIVITY_DATA_H_
|
||||
110
src/ballistica/python/class/python_class_collide_model.cc
Normal file
110
src/ballistica/python/class/python_class_collide_model.cc
Normal file
@ -0,0 +1,110 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/class/python_class_collide_model.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/game/game.h"
|
||||
#include "ballistica/media/component/collide_model.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
auto PythonClassCollideModel::tp_repr(PythonClassCollideModel* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Object::Ref<CollideModel> m = *(self->collide_model_);
|
||||
return Py_BuildValue(
|
||||
"s", (std::string("<ba.CollideModel ")
|
||||
+ (m.exists() ? ("\"" + m->name() + "\"") : "(empty ref)") + ">")
|
||||
.c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
void PythonClassCollideModel::SetupType(PyTypeObject* obj) {
|
||||
PythonClass::SetupType(obj);
|
||||
obj->tp_name = "ba.CollideModel";
|
||||
obj->tp_basicsize = sizeof(PythonClassCollideModel);
|
||||
obj->tp_doc =
|
||||
"A reference to a collide-model.\n"
|
||||
"\n"
|
||||
"Category: Asset Classes\n"
|
||||
"\n"
|
||||
"Use ba.getcollidemodel() to instantiate one.";
|
||||
obj->tp_repr = (reprfunc)tp_repr;
|
||||
obj->tp_new = tp_new;
|
||||
obj->tp_dealloc = (destructor)tp_dealloc;
|
||||
}
|
||||
|
||||
auto PythonClassCollideModel::Create(CollideModel* collide_model) -> PyObject* {
|
||||
s_create_empty_ = true; // prevent class from erroring on create
|
||||
auto* t = reinterpret_cast<PythonClassCollideModel*>(
|
||||
PyObject_CallObject(reinterpret_cast<PyObject*>(&type_obj), nullptr));
|
||||
s_create_empty_ = false;
|
||||
if (!t) {
|
||||
throw Exception("ba.CollideModel creation failed.");
|
||||
}
|
||||
*(t->collide_model_) = collide_model;
|
||||
return reinterpret_cast<PyObject*>(t);
|
||||
}
|
||||
|
||||
auto PythonClassCollideModel::GetCollideModel(bool doraise) const
|
||||
-> CollideModel* {
|
||||
CollideModel* collide_model = collide_model_->get();
|
||||
if (!collide_model && doraise) {
|
||||
throw Exception("Invalid CollideModel.", PyExcType::kNotFound);
|
||||
}
|
||||
return collide_model;
|
||||
}
|
||||
|
||||
auto PythonClassCollideModel::tp_new(PyTypeObject* type, PyObject* args,
|
||||
PyObject* kwds) -> PyObject* {
|
||||
auto* self =
|
||||
reinterpret_cast<PythonClassCollideModel*>(type->tp_alloc(type, 0));
|
||||
if (self) {
|
||||
BA_PYTHON_TRY;
|
||||
if (!InGameThread()) {
|
||||
throw Exception(
|
||||
"ERROR: " + std::string(type_obj.tp_name)
|
||||
+ " objects must only be created in the game thread (current is ("
|
||||
+ GetCurrentThreadName() + ").");
|
||||
}
|
||||
if (!s_create_empty_) {
|
||||
throw Exception(
|
||||
"Can't instantiate CollideModels directly; use "
|
||||
"ba.getcollidemodel() to get them.");
|
||||
}
|
||||
self->collide_model_ = new Object::Ref<CollideModel>();
|
||||
BA_PYTHON_NEW_CATCH;
|
||||
}
|
||||
return reinterpret_cast<PyObject*>(self);
|
||||
}
|
||||
|
||||
void PythonClassCollideModel::Delete(Object::Ref<CollideModel>* ref) {
|
||||
assert(InGameThread());
|
||||
// if we're the py-object for a collide_model, clear them out
|
||||
// (FIXME - we should pass the old pointer in here to sanity-test that we
|
||||
// were their ref)
|
||||
if (ref->exists()) {
|
||||
(*ref)->ClearPyObject();
|
||||
}
|
||||
delete ref;
|
||||
}
|
||||
|
||||
void PythonClassCollideModel::tp_dealloc(PythonClassCollideModel* self) {
|
||||
BA_PYTHON_TRY;
|
||||
// these have to be deleted in the game thread - send the ptr along if need
|
||||
// be; otherwise do it immediately
|
||||
if (!InGameThread()) {
|
||||
Object::Ref<CollideModel>* c = self->collide_model_;
|
||||
g_game->PushCall([c] { Delete(c); });
|
||||
} else {
|
||||
Delete(self->collide_model_);
|
||||
}
|
||||
BA_PYTHON_DEALLOC_CATCH;
|
||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
||||
}
|
||||
|
||||
bool PythonClassCollideModel::s_create_empty_ = false;
|
||||
PyTypeObject PythonClassCollideModel::type_obj;
|
||||
|
||||
} // namespace ballistica
|
||||
34
src/ballistica/python/class/python_class_collide_model.h
Normal file
34
src/ballistica/python/class/python_class_collide_model.h
Normal file
@ -0,0 +1,34 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_COLLIDE_MODEL_H_
|
||||
#define BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_COLLIDE_MODEL_H_
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/python/class/python_class.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class PythonClassCollideModel : public PythonClass {
|
||||
public:
|
||||
static auto type_name() -> const char* { return "CollideModel"; }
|
||||
static auto tp_repr(PythonClassCollideModel* self) -> PyObject*;
|
||||
static void SetupType(PyTypeObject* obj);
|
||||
static PyTypeObject type_obj;
|
||||
static auto Create(CollideModel* collide_model) -> PyObject*;
|
||||
static auto Check(PyObject* o) -> bool {
|
||||
return PyObject_TypeCheck(o, &type_obj);
|
||||
}
|
||||
auto GetCollideModel(bool doraise = true) const -> CollideModel*;
|
||||
|
||||
private:
|
||||
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* kwds)
|
||||
-> PyObject*;
|
||||
static void Delete(Object::Ref<CollideModel>* ref);
|
||||
static void tp_dealloc(PythonClassCollideModel* self);
|
||||
static bool s_create_empty_;
|
||||
Object::Ref<CollideModel>* collide_model_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_COLLIDE_MODEL_H_
|
||||
227
src/ballistica/python/class/python_class_context.cc
Normal file
227
src/ballistica/python/class/python_class_context.cc
Normal file
@ -0,0 +1,227 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/class/python_class_context.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/game/game.h"
|
||||
#include "ballistica/game/host_activity.h"
|
||||
#include "ballistica/game/session/host_session.h"
|
||||
#include "ballistica/python/python.h"
|
||||
#include "ballistica/ui/ui.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
void PythonClassContext::SetupType(PyTypeObject* obj) {
|
||||
PythonClass::SetupType(obj);
|
||||
obj->tp_name = "ba.Context";
|
||||
obj->tp_basicsize = sizeof(PythonClassContext);
|
||||
obj->tp_doc =
|
||||
"Context(source: Any)\n"
|
||||
"\n"
|
||||
"A game context state.\n"
|
||||
"\n"
|
||||
"Category: General Utility Classes\n"
|
||||
"\n"
|
||||
"Many operations such as ba.newnode() or ba.gettexture() operate\n"
|
||||
"implicitly on the current context. Each ba.Activity has its own\n"
|
||||
"Context and objects within that activity (nodes, media, etc) can only\n"
|
||||
"interact with other objects from that context.\n"
|
||||
"\n"
|
||||
"In general, as a modder, you should not need to worry about contexts,\n"
|
||||
"since timers and other callbacks will take care of saving and\n"
|
||||
"restoring the context automatically, but there may be rare cases where\n"
|
||||
"you need to deal with them, such as when loading media in for use in\n"
|
||||
"the UI (there is a special 'ui' context for all user-interface-related\n"
|
||||
"functionality)\n"
|
||||
"\n"
|
||||
"When instantiating a ba.Context instance, a single 'source' argument\n"
|
||||
"is passed, which can be one of the following strings/objects:\n\n"
|
||||
"'empty':\n"
|
||||
" Gives an empty context; it can be handy to run code here to ensure\n"
|
||||
" it does no loading of media, creation of nodes, etc.\n"
|
||||
"\n"
|
||||
"'current':\n"
|
||||
" Sets the context object to the current context.\n"
|
||||
"\n"
|
||||
"'ui':\n"
|
||||
" Sets to the UI context. UI functions as well as loading of media to\n"
|
||||
" be used in said functions must happen in the UI context.\n"
|
||||
"\n"
|
||||
"A ba.Activity instance:\n"
|
||||
" Gives the context for the provided ba.Activity.\n"
|
||||
" Most all code run during a game happens in an Activity's Context.\n"
|
||||
"\n"
|
||||
"A ba.Session instance:\n"
|
||||
" Gives the context for the provided ba.Session.\n"
|
||||
" Generally a user should not need to run anything here.\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"Usage:\n"
|
||||
"\n"
|
||||
"Contexts are generally used with the python 'with' statement, which\n"
|
||||
"sets the context as current on entry and resets it to the previous\n"
|
||||
"value on exit.\n"
|
||||
"\n"
|
||||
"# Example: load a few textures into the UI context\n"
|
||||
"# (for use in widgets, etc):\n"
|
||||
"with ba.Context('ui'):\n"
|
||||
" tex1 = ba.gettexture('foo_tex_1')\n"
|
||||
" tex2 = ba.gettexture('foo_tex_2')\n";
|
||||
|
||||
obj->tp_new = tp_new;
|
||||
obj->tp_dealloc = (destructor)tp_dealloc;
|
||||
obj->tp_repr = (reprfunc)tp_repr;
|
||||
obj->tp_richcompare = (richcmpfunc)tp_richcompare;
|
||||
obj->tp_methods = tp_methods;
|
||||
}
|
||||
|
||||
auto PythonClassContext::tp_repr(PythonClassContext* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
std::string context_str;
|
||||
if (self->context_->GetUIContext()) {
|
||||
context_str = "ui";
|
||||
} else if (HostActivity* ha = self->context_->GetHostActivity()) {
|
||||
PythonRef ha_obj(ha->GetPyActivity(), PythonRef::kAcquire);
|
||||
if (ha_obj.get() != Py_None) {
|
||||
context_str = ha_obj.Str();
|
||||
} else {
|
||||
context_str = ha->GetObjectDescription();
|
||||
}
|
||||
} else if (self->context_->target.exists()) {
|
||||
context_str = self->context_->target->GetObjectDescription();
|
||||
} else {
|
||||
context_str = "empty";
|
||||
}
|
||||
context_str = "<ba.Context (" + context_str + ")>";
|
||||
return PyUnicode_FromString(context_str.c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassContext::tp_richcompare(PythonClassContext* c1, PyObject* c2,
|
||||
int op) -> PyObject* {
|
||||
// always return false against other types
|
||||
if (!Check(c2)) {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
bool eq = (*(c1->context_)
|
||||
== *((reinterpret_cast<PythonClassContext*>(c2))->context_));
|
||||
if (op == Py_EQ) {
|
||||
if (eq) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
} else if (op == Py_NE) {
|
||||
if (!eq) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
} else {
|
||||
// don't support other ops
|
||||
Py_RETURN_NOTIMPLEMENTED;
|
||||
}
|
||||
}
|
||||
|
||||
auto PythonClassContext::tp_new(PyTypeObject* type, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
PyObject* source_obj = Py_None;
|
||||
if (!PyArg_ParseTuple(args, "O", &source_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!InGameThread()) {
|
||||
throw Exception(
|
||||
"ERROR: " + std::string(type_obj.tp_name)
|
||||
+ " objects must only be created in the game thread (current is ("
|
||||
+ GetCurrentThreadName() + ").");
|
||||
}
|
||||
|
||||
Context cs(nullptr);
|
||||
|
||||
if (Python::IsPyString(source_obj)) {
|
||||
std::string source = Python::GetPyString(source_obj);
|
||||
if (source == "ui") {
|
||||
cs = Context(g_game->GetUIContextTarget());
|
||||
} else if (source == "UI") {
|
||||
BA_LOG_ONCE("'UI' context-target option is deprecated; please use 'ui'");
|
||||
Python::PrintStackTrace();
|
||||
cs = Context(g_game->GetUIContextTarget());
|
||||
} else if (source == "current") {
|
||||
cs = Context::current();
|
||||
} else if (source == "empty") {
|
||||
cs = Context(nullptr);
|
||||
} else {
|
||||
throw Exception("invalid context identifier: '" + source + "'");
|
||||
}
|
||||
} else if (Python::IsPyHostActivity(source_obj)) {
|
||||
cs = Context(Python::GetPyHostActivity(source_obj));
|
||||
} else if (Python::IsPySession(source_obj)) {
|
||||
auto* hs = dynamic_cast<HostSession*>(Python::GetPySession(source_obj));
|
||||
assert(hs != nullptr);
|
||||
cs = Context(hs);
|
||||
} else {
|
||||
throw Exception(
|
||||
"Invalid argument to ba.Context(): " + Python::ObjToString(source_obj)
|
||||
+ "; expected 'ui', 'current', 'empty', a ba.Activity, or a "
|
||||
"ba.Session");
|
||||
}
|
||||
|
||||
auto* self = reinterpret_cast<PythonClassContext*>(type->tp_alloc(type, 0));
|
||||
if (self) {
|
||||
BA_PYTHON_TRY;
|
||||
self->context_ = new Context(cs);
|
||||
self->context_prev_ = new Context();
|
||||
BA_PYTHON_NEW_CATCH;
|
||||
}
|
||||
return reinterpret_cast<PyObject*>(self);
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
void PythonClassContext::tp_dealloc(PythonClassContext* self) {
|
||||
BA_PYTHON_TRY;
|
||||
// Contexts have to be deleted in the game thread;
|
||||
// ship them to it for deletion if need be; otherwise do it immediately.
|
||||
if (!InGameThread()) {
|
||||
Context* c = self->context_;
|
||||
Context* c2 = self->context_prev_;
|
||||
g_game->PushCall([c, c2] {
|
||||
delete c;
|
||||
delete c2;
|
||||
});
|
||||
} else {
|
||||
delete self->context_;
|
||||
delete self->context_prev_;
|
||||
}
|
||||
BA_PYTHON_DEALLOC_CATCH;
|
||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
||||
}
|
||||
|
||||
auto PythonClassContext::__enter__(PythonClassContext* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
*(self->context_prev_) = Context::current();
|
||||
Context::set_current(*(self->context_));
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassContext::__exit__(PythonClassContext* self, PyObject* args)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Context::set_current(*(self->context_prev_));
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
PyTypeObject PythonClassContext::type_obj;
|
||||
PyMethodDef PythonClassContext::tp_methods[] = {
|
||||
{"__enter__", (PyCFunction)__enter__, METH_NOARGS,
|
||||
"enter call for 'with' functionality"},
|
||||
{"__exit__", (PyCFunction)__exit__, METH_VARARGS,
|
||||
"exit call for 'with' functionality"},
|
||||
{nullptr}};
|
||||
|
||||
} // namespace ballistica
|
||||
37
src/ballistica/python/class/python_class_context.h
Normal file
37
src/ballistica/python/class/python_class_context.h
Normal file
@ -0,0 +1,37 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_CONTEXT_H_
|
||||
#define BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_CONTEXT_H_
|
||||
|
||||
#include "ballistica/core/context.h"
|
||||
#include "ballistica/python/class/python_class.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class PythonClassContext : public PythonClass {
|
||||
public:
|
||||
static auto type_name() -> const char* { return "Context"; }
|
||||
static void SetupType(PyTypeObject* obj);
|
||||
static auto Check(PyObject* o) -> bool {
|
||||
return PyObject_TypeCheck(o, &type_obj);
|
||||
}
|
||||
static PyTypeObject type_obj;
|
||||
auto context() const -> const Context& { return *context_; }
|
||||
|
||||
private:
|
||||
static PyMethodDef tp_methods[];
|
||||
static auto tp_repr(PythonClassContext* self) -> PyObject*;
|
||||
static auto tp_richcompare(PythonClassContext* c1, PyObject* c2, int op)
|
||||
-> PyObject*;
|
||||
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds)
|
||||
-> PyObject*;
|
||||
static void tp_dealloc(PythonClassContext* self);
|
||||
static auto __enter__(PythonClassContext* self) -> PyObject*;
|
||||
static auto __exit__(PythonClassContext* self, PyObject* args) -> PyObject*;
|
||||
Context* context_;
|
||||
Context* context_prev_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_CONTEXT_H_
|
||||
130
src/ballistica/python/class/python_class_context_call.cc
Normal file
130
src/ballistica/python/class/python_class_context_call.cc
Normal file
@ -0,0 +1,130 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/class/python_class_context_call.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/game/game.h"
|
||||
#include "ballistica/python/python_context_call.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
void PythonClassContextCall::SetupType(PyTypeObject* obj) {
|
||||
PythonClass::SetupType(obj);
|
||||
obj->tp_name = "ba.ContextCall";
|
||||
obj->tp_basicsize = sizeof(PythonClassContextCall);
|
||||
obj->tp_doc =
|
||||
"ContextCall(call: Callable)\n"
|
||||
"\n"
|
||||
"A context-preserving callable.\n"
|
||||
"\n"
|
||||
"Category: General Utility Classes\n"
|
||||
"\n"
|
||||
"A ContextCall wraps a callable object along with a reference\n"
|
||||
"to the current context (see ba.Context); it handles restoring the\n"
|
||||
"context when run and automatically clears itself if the context\n"
|
||||
"it belongs to shuts down.\n"
|
||||
"\n"
|
||||
"Generally you should not need to use this directly; all standard\n"
|
||||
"Ballistica callbacks involved with timers, materials, UI functions,\n"
|
||||
"etc. handle this under-the-hood you don't have to worry about it.\n"
|
||||
"The only time it may be necessary is if you are implementing your\n"
|
||||
"own callbacks, such as a worker thread that does some action and then\n"
|
||||
"runs some game code when done. By wrapping said callback in one of\n"
|
||||
"these, you can ensure that you will not inadvertently be keeping the\n"
|
||||
"current activity alive or running code in a torn-down (expired)\n"
|
||||
"context.\n"
|
||||
"\n"
|
||||
"You can also use ba.WeakCall for similar functionality, but\n"
|
||||
"ContextCall has the added bonus that it will not run during context\n"
|
||||
"shutdown, whereas ba.WeakCall simply looks at whether the target\n"
|
||||
"object still exists.\n"
|
||||
"\n"
|
||||
"# Example A: code like this can inadvertently prevent our activity\n"
|
||||
"# (self) from ending until the operation completes, since the bound\n"
|
||||
"# method we're passing (self.dosomething) contains a strong-reference\n"
|
||||
"# to self).\n"
|
||||
"start_some_long_action(callback_when_done=self.dosomething)\n"
|
||||
"\n"
|
||||
"# Example B: in this case our activity (self) can still die\n"
|
||||
"# properly; the callback will clear itself when the activity starts\n"
|
||||
"# shutting down, becoming a harmless no-op and releasing the reference\n"
|
||||
"# to our activity.\n"
|
||||
"start_long_action(callback_when_done=ba.ContextCall(self.mycallback))\n";
|
||||
|
||||
obj->tp_new = tp_new;
|
||||
obj->tp_dealloc = (destructor)tp_dealloc;
|
||||
obj->tp_repr = (reprfunc)tp_repr;
|
||||
obj->tp_methods = tp_methods;
|
||||
obj->tp_call = (ternaryfunc)tp_call;
|
||||
}
|
||||
|
||||
auto PythonClassContextCall::tp_call(PythonClassContextCall* self,
|
||||
PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
static const char* kwlist[] = {nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
|
||||
const_cast<char**>(kwlist)))
|
||||
return nullptr;
|
||||
|
||||
(*(self->context_call_))->Run();
|
||||
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassContextCall::tp_repr(PythonClassContextCall* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(self->context_call_->exists());
|
||||
return PyUnicode_FromString(
|
||||
("<ba.ContextCall call="
|
||||
+ (*(self->context_call_))->GetObjectDescription() + ">")
|
||||
.c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassContextCall::tp_new(PyTypeObject* type, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
auto* self =
|
||||
reinterpret_cast<PythonClassContextCall*>(type->tp_alloc(type, 0));
|
||||
if (self) {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
// try to do anything that may throw an exception before/during our
|
||||
// placement-new so we don't have to worry about tearing it down if
|
||||
// something goes wrong afterwards
|
||||
PyObject* source_obj = Py_None;
|
||||
if (!PyArg_ParseTuple(args, "O", &source_obj)) return nullptr;
|
||||
if (!InGameThread()) {
|
||||
throw Exception(
|
||||
"ERROR: " + std::string(type_obj.tp_name)
|
||||
+ " objects must only be created in the game thread (current is ("
|
||||
+ GetCurrentThreadName() + ").");
|
||||
}
|
||||
self->context_call_ = new Object::Ref<PythonContextCall>(
|
||||
Object::New<PythonContextCall>(source_obj));
|
||||
BA_PYTHON_NEW_CATCH;
|
||||
}
|
||||
return reinterpret_cast<PyObject*>(self);
|
||||
}
|
||||
|
||||
void PythonClassContextCall::tp_dealloc(PythonClassContextCall* self) {
|
||||
BA_PYTHON_TRY;
|
||||
// these have to be deleted in the game thread - send the ptr along if need
|
||||
// be; otherwise do it immediately
|
||||
if (!InGameThread()) {
|
||||
Object::Ref<PythonContextCall>* c = self->context_call_;
|
||||
g_game->PushCall([c] { delete c; });
|
||||
} else {
|
||||
delete self->context_call_;
|
||||
}
|
||||
BA_PYTHON_DEALLOC_CATCH;
|
||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
||||
}
|
||||
|
||||
PyTypeObject PythonClassContextCall::type_obj;
|
||||
PyMethodDef PythonClassContextCall::tp_methods[] = {{nullptr}};
|
||||
|
||||
} // namespace ballistica
|
||||
33
src/ballistica/python/class/python_class_context_call.h
Normal file
33
src/ballistica/python/class/python_class_context_call.h
Normal file
@ -0,0 +1,33 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_CONTEXT_CALL_H_
|
||||
#define BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_CONTEXT_CALL_H_
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/python/class/python_class.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class PythonClassContextCall : public PythonClass {
|
||||
public:
|
||||
static auto type_name() -> const char* { return "ContextCall"; }
|
||||
static void SetupType(PyTypeObject* obj);
|
||||
static auto Check(PyObject* o) -> bool {
|
||||
return PyObject_TypeCheck(o, &type_obj);
|
||||
}
|
||||
static PyTypeObject type_obj;
|
||||
|
||||
private:
|
||||
static PyMethodDef tp_methods[];
|
||||
static auto tp_call(PythonClassContextCall* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject*;
|
||||
static auto tp_repr(PythonClassContextCall* self) -> PyObject*;
|
||||
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds)
|
||||
-> PyObject*;
|
||||
static void tp_dealloc(PythonClassContextCall* self);
|
||||
Object::Ref<PythonContextCall>* context_call_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_CONTEXT_CALL_H_
|
||||
139
src/ballistica/python/class/python_class_data.cc
Normal file
139
src/ballistica/python/class/python_class_data.cc
Normal file
@ -0,0 +1,139 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/class/python_class_data.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/game/game.h"
|
||||
#include "ballistica/media/component/data.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
auto PythonClassData::tp_repr(PythonClassData* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Object::Ref<Data> m = *(self->data_);
|
||||
return Py_BuildValue(
|
||||
"s", (std::string("<ba.Data ")
|
||||
+ (m.exists() ? ("\"" + m->name() + "\"") : "(empty ref)") + ">")
|
||||
.c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
void PythonClassData::SetupType(PyTypeObject* obj) {
|
||||
PythonClass::SetupType(obj);
|
||||
obj->tp_name = "ba.Data";
|
||||
obj->tp_basicsize = sizeof(PythonClassData);
|
||||
obj->tp_doc =
|
||||
"A reference to a data object.\n"
|
||||
"\n"
|
||||
"Category: Asset Classes\n"
|
||||
"\n"
|
||||
"Use ba.getdata() to instantiate one.";
|
||||
obj->tp_repr = (reprfunc)tp_repr;
|
||||
obj->tp_new = tp_new;
|
||||
obj->tp_dealloc = (destructor)tp_dealloc;
|
||||
obj->tp_methods = tp_methods;
|
||||
}
|
||||
|
||||
auto PythonClassData::Create(Data* data) -> PyObject* {
|
||||
s_create_empty_ = true; // prevent class from erroring on create
|
||||
auto* t = reinterpret_cast<PythonClassData*>(
|
||||
PyObject_CallObject(reinterpret_cast<PyObject*>(&type_obj), nullptr));
|
||||
s_create_empty_ = false;
|
||||
if (!t) {
|
||||
throw Exception("ba.Data creation failed.");
|
||||
}
|
||||
*(t->data_) = data;
|
||||
return reinterpret_cast<PyObject*>(t);
|
||||
}
|
||||
|
||||
auto PythonClassData::GetData(bool doraise) const -> Data* {
|
||||
Data* data = data_->get();
|
||||
if (!data && doraise) {
|
||||
throw Exception("Invalid Data.", PyExcType::kNotFound);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
auto PythonClassData::tp_new(PyTypeObject* type, PyObject* args, PyObject* kwds)
|
||||
-> PyObject* {
|
||||
auto* self = reinterpret_cast<PythonClassData*>(type->tp_alloc(type, 0));
|
||||
if (self) {
|
||||
BA_PYTHON_TRY;
|
||||
if (!InGameThread()) {
|
||||
throw Exception(
|
||||
"ERROR: " + std::string(type_obj.tp_name)
|
||||
+ " objects must only be created in the game thread (current is ("
|
||||
+ GetCurrentThreadName() + ").");
|
||||
}
|
||||
if (!s_create_empty_) {
|
||||
throw Exception(
|
||||
"Can't instantiate Datas directly; use ba.getdata() to get "
|
||||
"them.");
|
||||
}
|
||||
self->data_ = new Object::Ref<Data>();
|
||||
BA_PYTHON_NEW_CATCH;
|
||||
}
|
||||
return reinterpret_cast<PyObject*>(self);
|
||||
}
|
||||
|
||||
void PythonClassData::Delete(Object::Ref<Data>* ref) {
|
||||
assert(InGameThread());
|
||||
|
||||
// if we're the py-object for a data, clear them out
|
||||
// (FIXME - wej should pass the old pointer in here to sanity-test that we
|
||||
// were their ref)
|
||||
if (ref->exists()) {
|
||||
(*ref)->ClearPyObject();
|
||||
}
|
||||
delete ref;
|
||||
}
|
||||
|
||||
void PythonClassData::tp_dealloc(PythonClassData* self) {
|
||||
BA_PYTHON_TRY;
|
||||
// these have to be deleted in the game thread - send the ptr along if need
|
||||
// be; otherwise do it immediately
|
||||
if (!InGameThread()) {
|
||||
Object::Ref<Data>* s = self->data_;
|
||||
g_game->PushCall([s] { Delete(s); });
|
||||
} else {
|
||||
Delete(self->data_);
|
||||
}
|
||||
BA_PYTHON_DEALLOC_CATCH;
|
||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
||||
}
|
||||
|
||||
auto PythonClassData::GetValue(PythonClassData* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Data* data = self->data_->get();
|
||||
if (data == nullptr) {
|
||||
throw Exception("Invalid data object.", PyExcType::kNotFound);
|
||||
}
|
||||
// haha really need to rename this class.
|
||||
DataData* datadata = data->data_data();
|
||||
datadata->Load();
|
||||
datadata->set_last_used_time(GetRealTime());
|
||||
PyObject* obj = datadata->object().get();
|
||||
assert(obj);
|
||||
Py_INCREF(obj);
|
||||
return obj;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
bool PythonClassData::s_create_empty_ = false;
|
||||
PyTypeObject PythonClassData::type_obj;
|
||||
|
||||
PyMethodDef PythonClassData::tp_methods[] = {
|
||||
{"getvalue", (PyCFunction)GetValue, METH_NOARGS,
|
||||
"getvalue() -> Any\n"
|
||||
"\n"
|
||||
"Return the data object's value.\n"
|
||||
"\n"
|
||||
"This can consist of anything representable by json (dicts, lists,\n"
|
||||
"numbers, bools, None, etc).\n"
|
||||
"Note that this call will block if the data has not yet been loaded,\n"
|
||||
"so it can be beneficial to plan a short bit of time between when\n"
|
||||
"the data object is requested and when it's value is accessed.\n"},
|
||||
{nullptr}};
|
||||
|
||||
} // namespace ballistica
|
||||
36
src/ballistica/python/class/python_class_data.h
Normal file
36
src/ballistica/python/class/python_class_data.h
Normal file
@ -0,0 +1,36 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_DATA_H_
|
||||
#define BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_DATA_H_
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/python/class/python_class.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class PythonClassData : public PythonClass {
|
||||
public:
|
||||
static auto type_name() -> const char* { return "Data"; }
|
||||
static PyTypeObject type_obj;
|
||||
static auto tp_repr(PythonClassData* self) -> PyObject*;
|
||||
static void SetupType(PyTypeObject* obj);
|
||||
static auto Create(Data* data) -> PyObject*;
|
||||
static auto Check(PyObject* o) -> bool {
|
||||
return PyObject_TypeCheck(o, &type_obj);
|
||||
}
|
||||
auto GetData(bool doraise = true) const -> Data*;
|
||||
|
||||
private:
|
||||
static PyMethodDef tp_methods[];
|
||||
static auto GetValue(PythonClassData* self) -> PyObject*;
|
||||
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* kwds)
|
||||
-> PyObject*;
|
||||
static void tp_dealloc(PythonClassData* self);
|
||||
static void Delete(Object::Ref<Data>* ref);
|
||||
static bool s_create_empty_;
|
||||
Object::Ref<Data>* data_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_DATA_H_
|
||||
446
src/ballistica/python/class/python_class_input_device.cc
Normal file
446
src/ballistica/python/class/python_class_input_device.cc
Normal file
@ -0,0 +1,446 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/class/python_class_input_device.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/game/player.h"
|
||||
#include "ballistica/input/device/input_device.h"
|
||||
#include "ballistica/input/device/keyboard_input.h"
|
||||
#include "ballistica/python/python.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Ignore a few things that python macros do.
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "RedundantCast"
|
||||
|
||||
void PythonClassInputDevice::SetupType(PyTypeObject* obj) {
|
||||
PythonClass::SetupType(obj);
|
||||
obj->tp_name = "ba.InputDevice";
|
||||
obj->tp_basicsize = sizeof(PythonClassInputDevice);
|
||||
obj->tp_doc =
|
||||
"An input-device such as a gamepad, touchscreen, or keyboard.\n"
|
||||
"\n"
|
||||
"Category: Gameplay Classes\n"
|
||||
"\n"
|
||||
"Attributes:\n"
|
||||
"\n"
|
||||
" allows_configuring: bool\n"
|
||||
" Whether the input-device can be configured.\n"
|
||||
"\n"
|
||||
" has_meaningful_button_names: bool\n"
|
||||
" Whether button names returned by this instance match labels\n"
|
||||
" on the actual device. (Can be used to determine whether to show\n"
|
||||
" them in controls-overlays, etc.)\n"
|
||||
"\n"
|
||||
" player: Optional[ba.SessionPlayer]\n"
|
||||
" The player associated with this input device.\n"
|
||||
"\n"
|
||||
" client_id: int\n"
|
||||
" The numeric client-id this device is associated with.\n"
|
||||
" This is only meaningful for remote client inputs; for\n"
|
||||
" all local devices this will be -1.\n"
|
||||
"\n"
|
||||
" name: str\n"
|
||||
" The name of the device.\n"
|
||||
"\n"
|
||||
" unique_identifier: str\n"
|
||||
" A string that can be used to persistently identify the device,\n"
|
||||
" even among other devices of the same type. Used for saving\n"
|
||||
" prefs, etc.\n"
|
||||
"\n"
|
||||
" id: int\n"
|
||||
" The unique numeric id of this device.\n"
|
||||
"\n"
|
||||
" instance_number: int\n"
|
||||
" The number of this device among devices of the same type.\n"
|
||||
"\n"
|
||||
" is_controller_app: bool\n"
|
||||
" Whether this input-device represents a locally-connected\n"
|
||||
" controller-app.\n"
|
||||
"\n"
|
||||
" is_remote_client: bool\n"
|
||||
" Whether this input-device represents a remotely-connected\n"
|
||||
" client.\n"
|
||||
"\n";
|
||||
|
||||
obj->tp_new = tp_new;
|
||||
obj->tp_dealloc = (destructor)tp_dealloc;
|
||||
obj->tp_repr = (reprfunc)tp_repr;
|
||||
obj->tp_methods = tp_methods;
|
||||
obj->tp_getattro = (getattrofunc)tp_getattro;
|
||||
obj->tp_setattro = (setattrofunc)tp_setattro;
|
||||
|
||||
// We provide number methods only for bool functionality.
|
||||
memset(&as_number_, 0, sizeof(as_number_));
|
||||
as_number_.nb_bool = (inquiry)nb_bool;
|
||||
obj->tp_as_number = &as_number_;
|
||||
}
|
||||
|
||||
auto PythonClassInputDevice::Create(InputDevice* input_device) -> PyObject* {
|
||||
// Make sure we only have one python ref per material.
|
||||
if (input_device) {
|
||||
assert(!input_device->has_py_ref());
|
||||
}
|
||||
auto* py_input_device = reinterpret_cast<PythonClassInputDevice*>(
|
||||
PyObject_CallObject(reinterpret_cast<PyObject*>(&type_obj), nullptr));
|
||||
if (!py_input_device) {
|
||||
throw Exception("ba.InputDevice creation failed.");
|
||||
}
|
||||
*(py_input_device->input_device_) = input_device;
|
||||
return reinterpret_cast<PyObject*>(py_input_device);
|
||||
}
|
||||
|
||||
auto PythonClassInputDevice::GetInputDevice() const -> InputDevice* {
|
||||
InputDevice* input_device = input_device_->get();
|
||||
if (!input_device) {
|
||||
throw Exception(PyExcType::kInputDeviceNotFound);
|
||||
}
|
||||
return input_device;
|
||||
}
|
||||
|
||||
auto PythonClassInputDevice::tp_repr(PythonClassInputDevice* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
InputDevice* d = self->input_device_->get();
|
||||
int input_device_id = d ? d->index() : -1;
|
||||
std::string dname = d ? d->GetDeviceName() : "invalid device";
|
||||
return Py_BuildValue("s",
|
||||
(std::string("<Ballistica InputDevice ")
|
||||
+ std::to_string(input_device_id) + " (" + dname + ")>")
|
||||
.c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassInputDevice::nb_bool(PythonClassInputDevice* self) -> int {
|
||||
return self->input_device_->exists();
|
||||
}
|
||||
|
||||
PyNumberMethods PythonClassInputDevice::as_number_;
|
||||
|
||||
auto PythonClassInputDevice::tp_new(PyTypeObject* type, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
auto* self =
|
||||
reinterpret_cast<PythonClassInputDevice*>(type->tp_alloc(type, 0));
|
||||
if (self) {
|
||||
BA_PYTHON_TRY;
|
||||
if (!InGameThread()) {
|
||||
throw Exception(
|
||||
"ERROR: " + std::string(type_obj.tp_name)
|
||||
+ " objects must only be created in the game thread (current is ("
|
||||
+ GetCurrentThreadName() + ").");
|
||||
}
|
||||
self->input_device_ = new Object::WeakRef<InputDevice>();
|
||||
BA_PYTHON_NEW_CATCH;
|
||||
}
|
||||
return reinterpret_cast<PyObject*>(self);
|
||||
}
|
||||
|
||||
void PythonClassInputDevice::tp_dealloc(PythonClassInputDevice* self) {
|
||||
BA_PYTHON_TRY;
|
||||
// These have to be destructed in the game thread - send them along to it if
|
||||
// need be.
|
||||
// FIXME: Technically the main thread has a pointer to a dead PyObject
|
||||
// until the delete goes through; could that ever be a problem?
|
||||
if (!InGameThread()) {
|
||||
Object::WeakRef<InputDevice>* d = self->input_device_;
|
||||
g_game->PushCall([d] { delete d; });
|
||||
} else {
|
||||
delete self->input_device_;
|
||||
}
|
||||
BA_PYTHON_DEALLOC_CATCH;
|
||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
||||
}
|
||||
|
||||
auto PythonClassInputDevice::tp_getattro(PythonClassInputDevice* self,
|
||||
PyObject* attr) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(PyUnicode_Check(attr)); // NOLINT (signed bitwise ops)
|
||||
const char* s = PyUnicode_AsUTF8(attr);
|
||||
if (!strcmp(s, "player")) {
|
||||
InputDevice* input_device = self->input_device_->get();
|
||||
if (!input_device) {
|
||||
throw Exception(PyExcType::kInputDeviceNotFound);
|
||||
}
|
||||
Player* player = input_device->GetPlayer();
|
||||
if (player != nullptr) {
|
||||
return player->NewPyRef();
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
} else if (!strcmp(s, "allows_configuring")) {
|
||||
InputDevice* d = self->input_device_->get();
|
||||
if (!d) {
|
||||
throw Exception(PyExcType::kInputDeviceNotFound);
|
||||
}
|
||||
if (d->GetAllowsConfiguring()) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
} else if (!strcmp(s, "has_meaningful_button_names")) {
|
||||
InputDevice* d = self->input_device_->get();
|
||||
if (!d) {
|
||||
throw Exception(PyExcType::kInputDeviceNotFound);
|
||||
}
|
||||
if (d->HasMeaningfulButtonNames()) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
} else if (!strcmp(s, "client_id")) {
|
||||
InputDevice* d = self->input_device_->get();
|
||||
if (!d) {
|
||||
throw Exception(PyExcType::kInputDeviceNotFound);
|
||||
}
|
||||
return PyLong_FromLong(d->GetClientID());
|
||||
} else if (!strcmp(s, "name")) {
|
||||
InputDevice* d = self->input_device_->get();
|
||||
if (!d) {
|
||||
throw Exception(PyExcType::kInputDeviceNotFound);
|
||||
}
|
||||
return PyUnicode_FromString(d->GetDeviceName().c_str());
|
||||
} else if (!strcmp(s, "unique_identifier")) {
|
||||
InputDevice* d = self->input_device_->get();
|
||||
if (!d) {
|
||||
throw Exception(PyExcType::kInputDeviceNotFound);
|
||||
}
|
||||
return PyUnicode_FromString(d->GetPersistentIdentifier().c_str());
|
||||
} else if (!strcmp(s, "id")) {
|
||||
InputDevice* d = self->input_device_->get();
|
||||
if (!d) {
|
||||
throw Exception(PyExcType::kInputDeviceNotFound);
|
||||
}
|
||||
return PyLong_FromLong(d->index());
|
||||
} else if (!strcmp(s, "instance_number")) {
|
||||
InputDevice* d = self->input_device_->get();
|
||||
if (!d) {
|
||||
throw Exception(PyExcType::kInputDeviceNotFound);
|
||||
}
|
||||
return PyLong_FromLong(d->device_number());
|
||||
} else if (!strcmp(s, "is_controller_app")) {
|
||||
InputDevice* input_device = self->input_device_->get();
|
||||
if (!input_device) {
|
||||
throw Exception(PyExcType::kInputDeviceNotFound);
|
||||
}
|
||||
if (input_device->IsRemoteApp()) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
} else if (!strcmp(s, "is_remote_client")) {
|
||||
InputDevice* input_device = self->input_device_->get();
|
||||
if (!input_device) {
|
||||
throw Exception(PyExcType::kInputDeviceNotFound);
|
||||
}
|
||||
if (input_device->IsRemoteClient()) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to generic behavior.
|
||||
PyObject* val;
|
||||
val = PyObject_GenericGetAttr(reinterpret_cast<PyObject*>(self), attr);
|
||||
return val;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassInputDevice::tp_setattro(PythonClassInputDevice* self,
|
||||
PyObject* attr, PyObject* val) -> int {
|
||||
BA_PYTHON_TRY;
|
||||
assert(PyUnicode_Check(attr)); // NOLINT (signed bitwise)
|
||||
throw Exception("Attr '" + std::string(PyUnicode_AsUTF8(attr))
|
||||
+ "' is not settable on input device objects.");
|
||||
// return PyObject_GenericSetAttr(reinterpret_cast<PyObject*>(self), attr,
|
||||
// val);
|
||||
BA_PYTHON_INT_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassInputDevice::RemoveRemotePlayerFromGame(
|
||||
PythonClassInputDevice* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
InputDevice* d = self->input_device_->get();
|
||||
if (!d) {
|
||||
throw Exception(PyExcType::kInputDeviceNotFound);
|
||||
}
|
||||
d->RemoveRemotePlayerFromGame();
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassInputDevice::GetDefaultPlayerName(PythonClassInputDevice* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
InputDevice* d = self->input_device_->get();
|
||||
if (!d) {
|
||||
throw Exception(PyExcType::kInputDeviceNotFound);
|
||||
}
|
||||
return PyUnicode_FromString(d->GetDefaultPlayerName().c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassInputDevice::GetPlayerProfiles(PythonClassInputDevice* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
InputDevice* d = self->input_device_->get();
|
||||
if (!d) {
|
||||
throw Exception(PyExcType::kInputDeviceNotFound);
|
||||
}
|
||||
if (PyObject* profiles = d->GetPlayerProfiles()) {
|
||||
Py_INCREF(profiles);
|
||||
return profiles;
|
||||
} else {
|
||||
return Py_BuildValue("{}"); // Empty dict.
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassInputDevice::GetAccountName(PythonClassInputDevice* self,
|
||||
PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
int full;
|
||||
static const char* kwlist[] = {"full", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "p",
|
||||
const_cast<char**>(kwlist), &full)) {
|
||||
return nullptr;
|
||||
}
|
||||
InputDevice* d = self->input_device_->get();
|
||||
if (!d) {
|
||||
throw Exception(PyExcType::kInputDeviceNotFound);
|
||||
}
|
||||
return PyUnicode_FromString(
|
||||
d->GetAccountName(static_cast<bool>(full)).c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassInputDevice::IsConnectedToRemotePlayer(
|
||||
PythonClassInputDevice* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
InputDevice* input_device = self->input_device_->get();
|
||||
if (!input_device) {
|
||||
throw Exception(PyExcType::kInputDeviceNotFound);
|
||||
}
|
||||
if (input_device->GetRemotePlayer()) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassInputDevice::Exists(PythonClassInputDevice* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
if (self->input_device_->exists()) {
|
||||
Py_RETURN_TRUE;
|
||||
}
|
||||
Py_RETURN_FALSE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassInputDevice::GetAxisName(PythonClassInputDevice* self,
|
||||
PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(InGameThread());
|
||||
int id;
|
||||
static const char* kwlist[] = {"axis_id", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "i",
|
||||
const_cast<char**>(kwlist), &id)) {
|
||||
return nullptr;
|
||||
}
|
||||
InputDevice* input_device = self->input_device_->get();
|
||||
if (!input_device) {
|
||||
throw Exception(PyExcType::kInputDeviceNotFound);
|
||||
}
|
||||
return PyUnicode_FromString(input_device->GetAxisName(id).c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassInputDevice::GetButtonName(PythonClassInputDevice* self,
|
||||
PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(InGameThread());
|
||||
int id{};
|
||||
static const char* kwlist[] = {"button_id", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "i",
|
||||
const_cast<char**>(kwlist), &id)) {
|
||||
return nullptr;
|
||||
}
|
||||
InputDevice* d = self->input_device_->get();
|
||||
if (!d) {
|
||||
throw Exception(PyExcType::kInputDeviceNotFound);
|
||||
}
|
||||
|
||||
// Ask the input-device for the button name.
|
||||
std::string bname = d->GetButtonName(id);
|
||||
|
||||
// If this doesn't appear to be lstr json itself, convert it to that.
|
||||
if (bname.length() < 1 || bname.c_str()[0] != '{') {
|
||||
Utils::StringReplaceAll(&bname, "\"", "\\\"");
|
||||
bname = R"({"v":")" + bname + "\"}";
|
||||
}
|
||||
PythonRef args2(Py_BuildValue("(s)", bname.c_str()), PythonRef::kSteal);
|
||||
PythonRef results =
|
||||
g_python->obj(Python::ObjID::kLstrFromJsonCall).Call(args2);
|
||||
return results.NewRef();
|
||||
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
PyTypeObject PythonClassInputDevice::type_obj;
|
||||
PyMethodDef PythonClassInputDevice::tp_methods[] = {
|
||||
{"remove_remote_player_from_game", (PyCFunction)RemoveRemotePlayerFromGame,
|
||||
METH_NOARGS,
|
||||
"remove_remote_player_from_game() -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
{"is_connected_to_remote_player", (PyCFunction)IsConnectedToRemotePlayer,
|
||||
METH_NOARGS,
|
||||
"is_connected_to_remote_player() -> bool\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
{"exists", (PyCFunction)Exists, METH_NOARGS,
|
||||
"exists() -> bool\n"
|
||||
"\n"
|
||||
"Return whether the underlying device for this object is still present."},
|
||||
{"get_button_name", (PyCFunction)GetButtonName,
|
||||
METH_VARARGS | METH_KEYWORDS, // NOLINT (signed bitwise ops)
|
||||
"get_button_name(button_id: int) -> ba.Lstr\n"
|
||||
"\n"
|
||||
"Given a button ID, return a human-readable name for that key/button.\n"
|
||||
"\n"
|
||||
"Can return an empty string if the value is not meaningful to humans."},
|
||||
// NOLINTNEXTLINE (signed bitwise ops)
|
||||
{"get_axis_name", (PyCFunction)GetAxisName, METH_VARARGS | METH_KEYWORDS,
|
||||
"get_axis_name(axis_id: int) -> str\n"
|
||||
"\n"
|
||||
"Given an axis ID, return the name of the axis on this device.\n"
|
||||
"\n"
|
||||
"Can return an empty string if the value is not meaningful to humans."},
|
||||
{"get_default_player_name", (PyCFunction)GetDefaultPlayerName, METH_NOARGS,
|
||||
"get_default_player_name() -> str\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Returns the default player name for this device. (used for the 'random'\n"
|
||||
"profile)"},
|
||||
{"get_account_name", (PyCFunction)GetAccountName,
|
||||
METH_VARARGS | METH_KEYWORDS, // NOLINT (signed bitwise ops)
|
||||
"get_account_name(full: bool) -> str\n"
|
||||
"\n"
|
||||
"Returns the account name associated with this device.\n"
|
||||
"\n"
|
||||
"(can be used to get account names for remote players)"},
|
||||
{"get_player_profiles", (PyCFunction)GetPlayerProfiles, METH_NOARGS,
|
||||
"get_player_profiles() -> dict\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
{nullptr}}; // namespace ballistica
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
} // namespace ballistica
|
||||
52
src/ballistica/python/class/python_class_input_device.h
Normal file
52
src/ballistica/python/class/python_class_input_device.h
Normal file
@ -0,0 +1,52 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_INPUT_DEVICE_H_
|
||||
#define BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_INPUT_DEVICE_H_
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/python/class/python_class.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class PythonClassInputDevice : public PythonClass {
|
||||
public:
|
||||
static auto type_name() -> const char* { return "InputDevice"; }
|
||||
static void SetupType(PyTypeObject* obj);
|
||||
static auto Create(InputDevice* input_device) -> PyObject*;
|
||||
static auto Check(PyObject* o) -> bool {
|
||||
return PyObject_TypeCheck(o, &type_obj);
|
||||
}
|
||||
static PyTypeObject type_obj;
|
||||
auto GetInputDevice() const -> InputDevice*;
|
||||
|
||||
private:
|
||||
static PyMethodDef tp_methods[];
|
||||
static auto tp_repr(PythonClassInputDevice* self) -> PyObject*;
|
||||
static auto tp_getattro(PythonClassInputDevice* self, PyObject* attr)
|
||||
-> PyObject*;
|
||||
static auto tp_setattro(PythonClassInputDevice* self, PyObject* attr,
|
||||
PyObject* val) -> int;
|
||||
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds)
|
||||
-> PyObject*;
|
||||
static void tp_dealloc(PythonClassInputDevice* self);
|
||||
static auto nb_bool(PythonClassInputDevice* self) -> int;
|
||||
static auto RemoveRemotePlayerFromGame(PythonClassInputDevice* self)
|
||||
-> PyObject*;
|
||||
static auto GetDefaultPlayerName(PythonClassInputDevice* self) -> PyObject*;
|
||||
static auto GetPlayerProfiles(PythonClassInputDevice* self) -> PyObject*;
|
||||
static auto GetAccountName(PythonClassInputDevice* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject*;
|
||||
static auto IsConnectedToRemotePlayer(PythonClassInputDevice* self)
|
||||
-> PyObject*;
|
||||
static auto Exists(PythonClassInputDevice* self) -> PyObject*;
|
||||
static auto GetAxisName(PythonClassInputDevice* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject*;
|
||||
static auto GetButtonName(PythonClassInputDevice* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject*;
|
||||
static PyNumberMethods as_number_;
|
||||
Object::WeakRef<InputDevice>* input_device_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_INPUT_DEVICE_H_
|
||||
712
src/ballistica/python/class/python_class_material.cc
Normal file
712
src/ballistica/python/class/python_class_material.cc
Normal file
@ -0,0 +1,712 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/class/python_class_material.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/dynamics/material/impact_sound_material_action.h"
|
||||
#include "ballistica/dynamics/material/material.h"
|
||||
#include "ballistica/dynamics/material/material_component.h"
|
||||
#include "ballistica/dynamics/material/material_condition_node.h"
|
||||
#include "ballistica/dynamics/material/node_message_material_action.h"
|
||||
#include "ballistica/dynamics/material/node_mod_material_action.h"
|
||||
#include "ballistica/dynamics/material/node_user_message_material_action.h"
|
||||
#include "ballistica/dynamics/material/part_mod_material_action.h"
|
||||
#include "ballistica/dynamics/material/python_call_material_action.h"
|
||||
#include "ballistica/dynamics/material/roll_sound_material_action.h"
|
||||
#include "ballistica/dynamics/material/skid_sound_material_action.h"
|
||||
#include "ballistica/dynamics/material/sound_material_action.h"
|
||||
#include "ballistica/game/game.h"
|
||||
#include "ballistica/game/host_activity.h"
|
||||
#include "ballistica/python/python.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Ignore signed bitwise stuff since python macros do a lot of it.
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "hicpp-signed-bitwise"
|
||||
#pragma ide diagnostic ignored "RedundantCast"
|
||||
|
||||
bool PythonClassMaterial::s_create_empty_ = false;
|
||||
PyTypeObject PythonClassMaterial::type_obj;
|
||||
|
||||
static void DoAddConditions(PyObject* cond_obj,
|
||||
Object::Ref<MaterialConditionNode>* c);
|
||||
static void DoAddAction(PyObject* actions_obj,
|
||||
std::vector<Object::Ref<MaterialAction> >* actions);
|
||||
|
||||
// Attrs we expose through our custom getattr/setattr.
|
||||
#define ATTR_LABEL "label"
|
||||
|
||||
// The set we expose via dir().
|
||||
static const char* extra_dir_attrs[] = {ATTR_LABEL, nullptr};
|
||||
|
||||
void PythonClassMaterial::SetupType(PyTypeObject* obj) {
|
||||
PythonClass::SetupType(obj);
|
||||
obj->tp_name = "ba.Material";
|
||||
obj->tp_repr = (reprfunc)tp_repr;
|
||||
obj->tp_basicsize = sizeof(PythonClassMaterial);
|
||||
|
||||
// clang-format off
|
||||
obj->tp_doc =
|
||||
"Material(label: str = None)\n"
|
||||
"\n"
|
||||
"An entity applied to game objects to modify collision behavior.\n"
|
||||
"\n"
|
||||
"Category: Gameplay Classes\n"
|
||||
"\n"
|
||||
"A material can affect physical characteristics, generate sounds,\n"
|
||||
"or trigger callback functions when collisions occur.\n"
|
||||
"\n"
|
||||
"Materials are applied to 'parts', which are groups of one or more\n"
|
||||
"rigid bodies created as part of a ba.Node. Nodes can have any number\n"
|
||||
"of parts, each with its own set of materials. Generally materials are\n"
|
||||
"specified as array attributes on the Node. The 'spaz' node, for\n"
|
||||
"example, has various attributes such as 'materials',\n"
|
||||
"'roller_materials', and 'punch_materials', which correspond to the\n"
|
||||
"various parts it creates.\n"
|
||||
"\n"
|
||||
"Use ba.Material() to instantiate a blank material, and then use its\n"
|
||||
"add_actions() method to define what the material does.\n"
|
||||
"\n"
|
||||
"Attributes:\n"
|
||||
"\n"
|
||||
" " ATTR_LABEL ": str\n"
|
||||
" A label for the material; only used for debugging.\n";
|
||||
// clang-format on
|
||||
|
||||
obj->tp_new = tp_new;
|
||||
obj->tp_dealloc = (destructor)tp_dealloc;
|
||||
obj->tp_methods = tp_methods;
|
||||
obj->tp_getattro = (getattrofunc)tp_getattro;
|
||||
obj->tp_setattro = (setattrofunc)tp_setattro;
|
||||
}
|
||||
|
||||
auto PythonClassMaterial::tp_new(PyTypeObject* type, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
auto* self = reinterpret_cast<PythonClassMaterial*>(type->tp_alloc(type, 0));
|
||||
if (self) {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
// Do anything that might throw an exception *before* our placement-new
|
||||
// stuff so we don't have to worry about cleaning it up on errors.
|
||||
if (!InGameThread()) {
|
||||
throw Exception(
|
||||
"ERROR: " + std::string(type_obj.tp_name)
|
||||
+ " objects must only be created in the game thread (current is ("
|
||||
+ GetCurrentThreadName() + ").");
|
||||
}
|
||||
PyObject* name_obj = Py_None;
|
||||
std::string name;
|
||||
Object::Ref<Material> m;
|
||||
if (!s_create_empty_) {
|
||||
static const char* kwlist[] = {"label", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "|O",
|
||||
const_cast<char**>(kwlist), &name_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (name_obj != Py_None) {
|
||||
name = Python::GetPyString(name_obj);
|
||||
} else {
|
||||
name = Python::GetPythonFileLocation();
|
||||
}
|
||||
|
||||
if (HostActivity* host_activity = Context::current().GetHostActivity()) {
|
||||
m = host_activity->NewMaterial(name);
|
||||
m->set_py_object(reinterpret_cast<PyObject*>(self));
|
||||
} else {
|
||||
throw Exception("Can't create materials in this context.",
|
||||
PyExcType::kContext);
|
||||
}
|
||||
}
|
||||
self->material_ = new Object::Ref<Material>(m);
|
||||
BA_PYTHON_NEW_CATCH;
|
||||
}
|
||||
return reinterpret_cast<PyObject*>(self);
|
||||
}
|
||||
|
||||
void PythonClassMaterial::Delete(Object::Ref<Material>* m) {
|
||||
assert(InGameThread());
|
||||
|
||||
// If we're the py-object for a material, clear them out.
|
||||
if (m->exists()) {
|
||||
assert((*m)->py_object() != nullptr);
|
||||
(*m)->set_py_object(nullptr);
|
||||
}
|
||||
delete m;
|
||||
}
|
||||
|
||||
void PythonClassMaterial::tp_dealloc(PythonClassMaterial* self) {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
// These have to be deleted in the game thread - push a call if
|
||||
// need be.. otherwise do it immediately.
|
||||
if (!InGameThread()) {
|
||||
Object::Ref<Material>* ptr = self->material_;
|
||||
g_game->PushCall([ptr] { Delete(ptr); });
|
||||
} else {
|
||||
Delete(self->material_);
|
||||
}
|
||||
BA_PYTHON_DEALLOC_CATCH;
|
||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
||||
}
|
||||
|
||||
auto PythonClassMaterial::tp_repr(PythonClassMaterial* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
return Py_BuildValue(
|
||||
"s",
|
||||
std::string("<ba.Material at " + Utils::PtrToString(self) + ">").c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassMaterial::tp_getattro(PythonClassMaterial* self, PyObject* attr)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
// Assuming this will always be a str?
|
||||
assert(PyUnicode_Check(attr));
|
||||
|
||||
const char* s = PyUnicode_AsUTF8(attr);
|
||||
|
||||
if (!strcmp(s, ATTR_LABEL)) {
|
||||
Material* material = self->material_->get();
|
||||
if (!material) {
|
||||
throw Exception("Invalid Material.", PyExcType::kNotFound);
|
||||
}
|
||||
return PyUnicode_FromString(material->label().c_str());
|
||||
}
|
||||
|
||||
// Fall back to generic behavior.
|
||||
PyObject* val;
|
||||
val = PyObject_GenericGetAttr(reinterpret_cast<PyObject*>(self), attr);
|
||||
return val;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassMaterial::tp_setattro(PythonClassMaterial* self, PyObject* attr,
|
||||
PyObject* val) -> int {
|
||||
BA_PYTHON_TRY;
|
||||
assert(PyUnicode_Check(attr));
|
||||
|
||||
throw Exception("Attr '" + std::string(PyUnicode_AsUTF8(attr))
|
||||
+ "' is not settable on Material objects.",
|
||||
PyExcType::kAttribute);
|
||||
|
||||
// return PyObject_GenericSetAttr(reinterpret_cast<PyObject*>(self), attr,
|
||||
// val);
|
||||
BA_PYTHON_INT_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassMaterial::Dir(PythonClassMaterial* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
// Start with the standard python dir listing.
|
||||
PyObject* dir_list = Python::generic_dir(reinterpret_cast<PyObject*>(self));
|
||||
assert(PyList_Check(dir_list));
|
||||
|
||||
// ..and add in our custom attr names.
|
||||
for (const char** name = extra_dir_attrs; *name != nullptr; name++) {
|
||||
PyList_Append(
|
||||
dir_list,
|
||||
PythonRef(PyUnicode_FromString(*name), PythonRef::kSteal).get());
|
||||
}
|
||||
PyList_Sort(dir_list);
|
||||
return dir_list;
|
||||
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassMaterial::AddActions(PythonClassMaterial* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(InGameThread());
|
||||
PyObject* conditions_obj{Py_None};
|
||||
PyObject* actions_obj{nullptr};
|
||||
const char* kwlist[] = {"actions", "conditions", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|O",
|
||||
const_cast<char**>(kwlist), &actions_obj,
|
||||
&conditions_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Object::Ref<MaterialConditionNode> conditions;
|
||||
if (conditions_obj != Py_None) {
|
||||
DoAddConditions(conditions_obj, &conditions);
|
||||
}
|
||||
|
||||
Material* m = self->material_->get();
|
||||
if (!m) {
|
||||
throw Exception("Invalid Material.", PyExcType::kNotFound);
|
||||
}
|
||||
std::vector<Object::Ref<MaterialAction> > actions;
|
||||
if (PyTuple_Check(actions_obj)) {
|
||||
Py_ssize_t size = PyTuple_GET_SIZE(actions_obj);
|
||||
if (size > 0) {
|
||||
// If the first item is a string, process this tuple as a single action.
|
||||
if (PyUnicode_Check(PyTuple_GET_ITEM(actions_obj, 0))) {
|
||||
DoAddAction(actions_obj, &actions);
|
||||
} else {
|
||||
// Otherwise each item is assumed to be an action.
|
||||
for (Py_ssize_t i = 0; i < size; i++) {
|
||||
DoAddAction(PyTuple_GET_ITEM(actions_obj, i), &actions);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PyErr_SetString(PyExc_AttributeError,
|
||||
"expected a tuple for \"actions\" argument");
|
||||
return nullptr;
|
||||
}
|
||||
m->AddComponent(Object::New<MaterialComponent>(conditions, actions));
|
||||
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
PyMethodDef PythonClassMaterial::tp_methods[] = {
|
||||
{"add_actions", (PyCFunction)AddActions, METH_VARARGS | METH_KEYWORDS,
|
||||
"add_actions(actions: Tuple, conditions: Optional[Tuple] = None)\n"
|
||||
" -> None\n"
|
||||
"\n"
|
||||
"Add one or more actions to the material, optionally with conditions.\n"
|
||||
"\n"
|
||||
"Conditions:\n"
|
||||
"\n"
|
||||
"Conditions are provided as tuples which can be combined to form boolean\n"
|
||||
"logic. A single condition might look like ('condition_name', cond_arg),\n"
|
||||
"or a more complex nested one might look like (('some_condition',\n"
|
||||
"cond_arg), 'or', ('another_condition', cond2_arg)).\n"
|
||||
"\n"
|
||||
"'and', 'or', and 'xor' are available to chain together 2 conditions, as\n"
|
||||
" seen above.\n"
|
||||
"\n"
|
||||
"Available Conditions:\n"
|
||||
"\n"
|
||||
"('they_have_material', material) - does the part we\'re hitting have a\n"
|
||||
" given ba.Material?\n"
|
||||
"\n"
|
||||
"('they_dont_have_material', material) - does the part we\'re hitting\n"
|
||||
" not have a given ba.Material?\n"
|
||||
"\n"
|
||||
"('eval_colliding') - is 'collide' true at this point in material\n"
|
||||
" evaluation? (see the modify_part_collision action)\n"
|
||||
"\n"
|
||||
"('eval_not_colliding') - is 'collide' false at this point in material\n"
|
||||
" evaluation? (see the modify_part_collision action)\n"
|
||||
"\n"
|
||||
"('we_are_younger_than', age) - is our part younger than 'age'\n"
|
||||
" (in milliseconds)?\n"
|
||||
"\n"
|
||||
"('we_are_older_than', age) - is our part older than 'age'\n"
|
||||
" (in milliseconds)?\n"
|
||||
"\n"
|
||||
"('they_are_younger_than', age) - is the part we're hitting younger than\n"
|
||||
" 'age' (in milliseconds)?\n"
|
||||
"\n"
|
||||
"('they_are_older_than', age) - is the part we're hitting older than\n"
|
||||
" 'age' (in milliseconds)?\n"
|
||||
"\n"
|
||||
"('they_are_same_node_as_us') - does the part we're hitting belong to\n"
|
||||
" the same ba.Node as us?\n"
|
||||
"\n"
|
||||
"('they_are_different_node_than_us') - does the part we're hitting\n"
|
||||
" belong to a different ba.Node than us?\n"
|
||||
"\n"
|
||||
"Actions:\n"
|
||||
"\n"
|
||||
"In a similar manner, actions are specified as tuples. Multiple actions\n"
|
||||
"can be specified by providing a tuple of tuples.\n"
|
||||
"\n"
|
||||
"Available Actions:\n"
|
||||
"\n"
|
||||
"('call', when, callable) - calls the provided callable; 'when' can be\n"
|
||||
" either 'at_connect' or 'at_disconnect'. 'at_connect' means to fire\n"
|
||||
" when the two parts first come in contact; 'at_disconnect' means to\n"
|
||||
" fire once they cease being in contact.\n"
|
||||
"\n"
|
||||
"('message', who, when, message_obj) - sends a message object; 'who' can\n"
|
||||
" be either 'our_node' or 'their_node', 'when' can be 'at_connect' or\n"
|
||||
" 'at_disconnect', and message_obj is the message object to send.\n"
|
||||
" This has the same effect as calling the node's handlemessage()\n"
|
||||
" method.\n"
|
||||
"\n"
|
||||
"('modify_part_collision', attr, value) - changes some characteristic\n"
|
||||
" of the physical collision that will occur between our part and their\n"
|
||||
" part. This change will remain in effect as long as the two parts\n"
|
||||
" remain overlapping. This means if you have a part with a material\n"
|
||||
" that turns 'collide' off against parts younger than 100ms, and it\n"
|
||||
" touches another part that is 50ms old, it will continue to not\n"
|
||||
" collide with that part until they separate, even if the 100ms\n"
|
||||
" threshold is passed. Options for attr/value are: 'physical' (boolean\n"
|
||||
" value; whether a *physical* response will occur at all), 'friction'\n"
|
||||
" (float value; how friction-y the physical response will be),\n"
|
||||
" 'collide' (boolean value; whether *any* collision will occur at all,\n"
|
||||
" including non-physical stuff like callbacks), 'use_node_collide'\n"
|
||||
" (boolean value; whether to honor modify_node_collision overrides for\n"
|
||||
" this collision), 'stiffness' (float value, how springy the physical\n"
|
||||
" response is), 'damping' (float value, how damped the physical\n"
|
||||
" response is), 'bounce' (float value; how bouncy the physical response\n"
|
||||
" is).\n"
|
||||
"\n"
|
||||
"('modify_node_collision', attr, value) - similar to\n"
|
||||
" modify_part_collision, but operates at a node-level.\n"
|
||||
" collision attributes set here will remain in effect as long as\n"
|
||||
" *anything* from our part's node and their part's node overlap.\n"
|
||||
" A key use of this functionality is to prevent new nodes from\n"
|
||||
" colliding with each other if they appear overlapped;\n"
|
||||
" if modify_part_collision is used, only the individual parts that\n"
|
||||
" were overlapping would avoid contact, but other parts could still\n"
|
||||
" contact leaving the two nodes 'tangled up'. Using\n"
|
||||
" modify_node_collision ensures that the nodes must completely\n"
|
||||
" separate before they can start colliding. Currently the only attr\n"
|
||||
" available here is 'collide' (a boolean value).\n"
|
||||
"\n"
|
||||
"('sound', sound, volume) - plays a ba.Sound when a collision occurs, at\n"
|
||||
" a given volume, regardless of the collision speed/etc.\n"
|
||||
"\n"
|
||||
"('impact_sound', sound, targetImpulse, volume) - plays a sound when a\n"
|
||||
" collision occurs, based on the speed of impact. Provide a ba.Sound, a\n"
|
||||
" target-impulse, and a volume.\n"
|
||||
"\n"
|
||||
"('skid_sound', sound, targetImpulse, volume) - plays a sound during a\n"
|
||||
" collision when parts are 'scraping' against each other. Provide a\n"
|
||||
" ba.Sound, a target-impulse, and a volume.\n"
|
||||
"\n"
|
||||
"('roll_sound', sound, targetImpulse, volume) - plays a sound during a\n"
|
||||
" collision when parts are 'rolling' against each other. Provide a\n"
|
||||
" ba.Sound, a target-impulse, and a volume.\n"
|
||||
"\n"
|
||||
"# example 1: create a material that lets us ignore\n"
|
||||
"# collisions against any nodes we touch in the first\n"
|
||||
"# 100 ms of our existence; handy for preventing us from\n"
|
||||
"# exploding outward if we spawn on top of another object:\n"
|
||||
"m = ba.Material()\n"
|
||||
"m.add_actions(conditions=(('we_are_younger_than', 100),\n"
|
||||
" 'or',('they_are_younger_than', 100)),\n"
|
||||
" actions=('modify_node_collision', 'collide', False))\n"
|
||||
"\n"
|
||||
"# example 2: send a DieMessage to anything we touch, but cause\n"
|
||||
"# no physical response. This should cause any ba.Actor to drop dead:\n"
|
||||
"m = ba.Material()\n"
|
||||
"m.add_actions(actions=(('modify_part_collision', 'physical', False),\n"
|
||||
" ('message', 'their_node', 'at_connect',\n"
|
||||
" ba.DieMessage())))\n"
|
||||
"\n"
|
||||
"# example 3: play some sounds when we're contacting the ground:\n"
|
||||
"m = ba.Material()\n"
|
||||
"m.add_actions(conditions=('they_have_material',\n"
|
||||
" shared.footing_material),\n"
|
||||
" actions=(('impact_sound', ba.getsound('metalHit'), 2, 5),\n"
|
||||
" ('skid_sound', ba.getsound('metalSkid'), 2, 5)))\n"
|
||||
"\n"},
|
||||
{"__dir__", (PyCFunction)Dir, METH_NOARGS,
|
||||
"allows inclusion of our custom attrs in standard python dir()"},
|
||||
|
||||
{nullptr}};
|
||||
|
||||
void DoAddConditions(PyObject* cond_obj,
|
||||
Object::Ref<MaterialConditionNode>* c) {
|
||||
assert(InGameThread());
|
||||
if (PyTuple_Check(cond_obj)) {
|
||||
Py_ssize_t size = PyTuple_GET_SIZE(cond_obj);
|
||||
if (size < 1) {
|
||||
throw Exception("Malformed arguments.", PyExcType::kValue);
|
||||
}
|
||||
|
||||
PyObject* first = PyTuple_GET_ITEM(cond_obj, 0);
|
||||
assert(first);
|
||||
|
||||
// If the first element is a string,
|
||||
// its a leaf node; process its elements as a single statement.
|
||||
if (PyUnicode_Check(first)) {
|
||||
(*c) = Object::New<MaterialConditionNode>();
|
||||
(*c)->opmode = MaterialConditionNode::OpMode::LEAF_NODE;
|
||||
int argc;
|
||||
const char* cond_str = PyUnicode_AsUTF8(first);
|
||||
bool first_arg_is_material = false;
|
||||
if (!strcmp(cond_str, "they_have_material")) {
|
||||
argc = 1;
|
||||
first_arg_is_material = true;
|
||||
(*c)->cond = MaterialCondition::kDstIsMaterial;
|
||||
} else if (!strcmp(cond_str, "they_dont_have_material")) {
|
||||
argc = 1;
|
||||
first_arg_is_material = true;
|
||||
(*c)->cond = MaterialCondition::kDstNotMaterial;
|
||||
} else if (!strcmp(cond_str, "eval_colliding")) {
|
||||
argc = 0;
|
||||
(*c)->cond = MaterialCondition::kEvalColliding;
|
||||
} else if (!strcmp(cond_str, "eval_not_colliding")) {
|
||||
argc = 0;
|
||||
(*c)->cond = MaterialCondition::kEvalNotColliding;
|
||||
} else if (!strcmp(cond_str, "we_are_younger_than")) {
|
||||
argc = 1;
|
||||
(*c)->cond = MaterialCondition::kSrcYoungerThan;
|
||||
} else if (!strcmp(cond_str, "we_are_older_than")) {
|
||||
argc = 1;
|
||||
(*c)->cond = MaterialCondition::kSrcOlderThan;
|
||||
} else if (!strcmp(cond_str, "they_are_younger_than")) {
|
||||
argc = 1;
|
||||
(*c)->cond = MaterialCondition::kDstYoungerThan;
|
||||
} else if (!strcmp(cond_str, "they_are_older_than")) {
|
||||
argc = 1;
|
||||
(*c)->cond = MaterialCondition::kDstOlderThan;
|
||||
} else if (!strcmp(cond_str, "they_are_same_node_as_us")) {
|
||||
argc = 0;
|
||||
(*c)->cond = MaterialCondition::kSrcDstSameNode;
|
||||
} else if (!strcmp(cond_str, "they_are_different_node_than_us")) {
|
||||
argc = 0;
|
||||
(*c)->cond = MaterialCondition::kSrcDstDiffNode;
|
||||
} else {
|
||||
throw Exception(
|
||||
std::string("Invalid material condition: \"") + cond_str + "\".",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
if (size != (argc + 1)) {
|
||||
throw Exception(
|
||||
std::string("Wrong number of arguments for condition: \"")
|
||||
+ cond_str + "\".",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
if (argc > 0) {
|
||||
if (first_arg_is_material) {
|
||||
(*c)->val1_material =
|
||||
Python::GetPyMaterial(PyTuple_GET_ITEM(cond_obj, 1));
|
||||
} else {
|
||||
PyObject* o = PyTuple_GET_ITEM(cond_obj, 1);
|
||||
if (!PyLong_Check(o)) {
|
||||
throw Exception(
|
||||
std::string("Expected int for first arg of condition: \"")
|
||||
+ cond_str + "\".",
|
||||
PyExcType::kType);
|
||||
}
|
||||
(*c)->val1 = static_cast<uint32_t>(PyLong_AsLong(o));
|
||||
}
|
||||
}
|
||||
if (argc > 1) {
|
||||
PyObject* o = PyTuple_GET_ITEM(cond_obj, 2);
|
||||
if (!PyLong_Check(o)) {
|
||||
throw Exception(
|
||||
std::string("Expected int for second arg of condition: \"")
|
||||
+ cond_str + "\".",
|
||||
PyExcType::kType);
|
||||
}
|
||||
(*c)->val1 = static_cast<uint32_t>(PyLong_AsLong(o));
|
||||
}
|
||||
} else if (PyTuple_Check(first)) {
|
||||
// First item is a tuple - assume its a tuple of size 3+2*n
|
||||
// containing tuples for odd index vals and operators for even.
|
||||
if (size < 3 || (size % 2 != 1)) {
|
||||
throw Exception("Malformed conditional statement.", PyExcType::kValue);
|
||||
}
|
||||
Object::Ref<MaterialConditionNode> c2;
|
||||
Object::Ref<MaterialConditionNode> c2_prev;
|
||||
for (Py_ssize_t i = 0; i < (size - 1); i += 2) {
|
||||
c2 = Object::New<MaterialConditionNode>();
|
||||
if (c2_prev.exists()) {
|
||||
c2->left_child = c2_prev;
|
||||
} else {
|
||||
DoAddConditions(PyTuple_GET_ITEM(cond_obj, i), &c2->left_child);
|
||||
}
|
||||
DoAddConditions(PyTuple_GET_ITEM(cond_obj, i + 2), &c2->right_child);
|
||||
|
||||
// Pull a string from between to set up our opmode with.
|
||||
std::string opmode_str =
|
||||
Python::GetPyString(PyTuple_GET_ITEM(cond_obj, i + 1));
|
||||
const char* opmode = opmode_str.c_str();
|
||||
if (!strcmp(opmode, "&&") || !strcmp(opmode, "and")) {
|
||||
c2->opmode = MaterialConditionNode::OpMode::AND_OPERATOR;
|
||||
} else if (!strcmp(opmode, "||") || !strcmp(opmode, "or")) {
|
||||
c2->opmode = MaterialConditionNode::OpMode::OR_OPERATOR;
|
||||
} else if (!strcmp(opmode, "^") || !strcmp(opmode, "xor")) {
|
||||
c2->opmode = MaterialConditionNode::OpMode::XOR_OPERATOR;
|
||||
} else {
|
||||
throw Exception(
|
||||
std::string("Invalid conditional operator: \"") + opmode + "\".",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
c2_prev = c2;
|
||||
}
|
||||
// Keep our lowest level.
|
||||
(*c) = c2;
|
||||
}
|
||||
} else {
|
||||
throw Exception("Conditions argument not a tuple.", PyExcType::kType);
|
||||
}
|
||||
}
|
||||
|
||||
void DoAddAction(PyObject* actions_obj,
|
||||
std::vector<Object::Ref<MaterialAction> >* actions) {
|
||||
assert(InGameThread());
|
||||
if (!PyTuple_Check(actions_obj)) {
|
||||
throw Exception("Expected a tuple.", PyExcType::kType);
|
||||
}
|
||||
Py_ssize_t size = PyTuple_GET_SIZE(actions_obj);
|
||||
assert(size > 0);
|
||||
PyObject* obj = PyTuple_GET_ITEM(actions_obj, 0);
|
||||
std::string type = Python::GetPyString(obj);
|
||||
if (type == "call") {
|
||||
if (size != 3) {
|
||||
throw Exception("Expected 3 values for command action tuple.",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
std::string when = Python::GetPyString(PyTuple_GET_ITEM(actions_obj, 1));
|
||||
bool at_disconnect;
|
||||
if (when == "at_connect") {
|
||||
at_disconnect = false;
|
||||
} else if (when == "at_disconnect") {
|
||||
at_disconnect = true;
|
||||
} else {
|
||||
throw Exception("Invalid command execution time: '" + when + "'.",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
PyObject* call_obj = PyTuple_GET_ITEM(actions_obj, 2);
|
||||
(*actions).push_back(Object::New<MaterialAction, PythonCallMaterialAction>(
|
||||
at_disconnect, call_obj));
|
||||
} else if (type == "message") {
|
||||
if (size < 4) {
|
||||
throw Exception("Expected >= 4 values for message action tuple.",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
std::string target = Python::GetPyString(PyTuple_GET_ITEM(actions_obj, 1));
|
||||
bool target_other_val;
|
||||
if (target == "our_node") {
|
||||
target_other_val = false;
|
||||
} else if (target == "their_node") {
|
||||
target_other_val = true;
|
||||
} else {
|
||||
throw Exception("Invalid message target: '" + target + "'.",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
std::string when = Python::GetPyString(PyTuple_GET_ITEM(actions_obj, 2));
|
||||
bool at_disconnect;
|
||||
if (when == "at_connect") {
|
||||
at_disconnect = false;
|
||||
} else if (when == "at_disconnect") {
|
||||
at_disconnect = true;
|
||||
} else {
|
||||
throw Exception("Invalid command execution time: '" + when + "'.",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
|
||||
// Pull the rest of the message.
|
||||
Buffer<char> b;
|
||||
PyObject* user_message_obj = nullptr;
|
||||
Python::DoBuildNodeMessage(actions_obj, 3, &b, &user_message_obj);
|
||||
if (user_message_obj) {
|
||||
(*actions).push_back(
|
||||
Object::New<MaterialAction, NodeUserMessageMaterialAction>(
|
||||
target_other_val, at_disconnect, user_message_obj));
|
||||
} else if (b.size() > 0) {
|
||||
(*actions).push_back(
|
||||
Object::New<MaterialAction, NodeMessageMaterialAction>(
|
||||
target_other_val, at_disconnect, b.data(), b.size()));
|
||||
}
|
||||
} else if (type == "modify_node_collision") {
|
||||
if (size != 3) {
|
||||
throw Exception(
|
||||
"Expected 3 values for modify_node_collision action tuple.",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
std::string attr = Python::GetPyString(PyTuple_GET_ITEM(actions_obj, 1));
|
||||
NodeCollideAttr attr_type;
|
||||
if (attr == "collide") {
|
||||
attr_type = NodeCollideAttr::kCollideNode;
|
||||
} else {
|
||||
throw Exception("Invalid node mod attr: '" + attr + "'.",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
|
||||
// Pull value.
|
||||
float val = Python::GetPyFloat(PyTuple_GET_ITEM(actions_obj, 2));
|
||||
(*actions).push_back(
|
||||
Object::New<MaterialAction, NodeModMaterialAction>(attr_type, val));
|
||||
} else if (type == "modify_part_collision") {
|
||||
if (size != 3) {
|
||||
throw Exception(
|
||||
"Expected 3 values for modify_part_collision action tuple.",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
PartCollideAttr attr_type;
|
||||
std::string attr = Python::GetPyString(PyTuple_GET_ITEM(actions_obj, 1));
|
||||
if (attr == "physical") {
|
||||
attr_type = PartCollideAttr::kPhysical;
|
||||
} else if (attr == "friction") {
|
||||
attr_type = PartCollideAttr::kFriction;
|
||||
} else if (attr == "collide") {
|
||||
attr_type = PartCollideAttr::kCollide;
|
||||
} else if (attr == "use_node_collide") {
|
||||
attr_type = PartCollideAttr::kUseNodeCollide;
|
||||
} else if (attr == "stiffness") {
|
||||
attr_type = PartCollideAttr::kStiffness;
|
||||
} else if (attr == "damping") {
|
||||
attr_type = PartCollideAttr::kDamping;
|
||||
} else if (attr == "bounce") {
|
||||
attr_type = PartCollideAttr::kBounce;
|
||||
} else {
|
||||
throw Exception("Invalid part mod attr: '" + attr + "'.",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
float val = Python::GetPyFloat(PyTuple_GET_ITEM(actions_obj, 2));
|
||||
(*actions).push_back(
|
||||
Object::New<MaterialAction, PartModMaterialAction>(attr_type, val));
|
||||
} else if (type == "sound") {
|
||||
if (size != 3) {
|
||||
throw Exception("Expected 3 values for sound action tuple.",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
Sound* sound = Python::GetPySound(PyTuple_GET_ITEM(actions_obj, 1));
|
||||
float volume = Python::GetPyFloat(PyTuple_GET_ITEM(actions_obj, 2));
|
||||
(*actions).push_back(
|
||||
Object::New<MaterialAction, SoundMaterialAction>(sound, volume));
|
||||
} else if (type == "impact_sound") {
|
||||
if (size != 4) {
|
||||
throw Exception("Expected 4 values for impact_sound action tuple.",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
PyObject* sounds_obj = PyTuple_GET_ITEM(actions_obj, 1);
|
||||
std::vector<Sound*> sounds;
|
||||
if (PySequence_Check(sounds_obj)) {
|
||||
sounds = Python::GetPySounds(sounds_obj); // Sequence of sounds.
|
||||
} else {
|
||||
sounds.push_back(Python::GetPySound(sounds_obj)); // Single sound.
|
||||
}
|
||||
if (sounds.empty()) {
|
||||
throw Exception("Require at least 1 sound.", PyExcType::kValue);
|
||||
}
|
||||
if (Utils::HasNullMembers(sounds)) {
|
||||
throw Exception("One or more invalid sound refs passed.",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
float target_impulse = Python::GetPyFloat(PyTuple_GET_ITEM(actions_obj, 2));
|
||||
float volume = Python::GetPyFloat(PyTuple_GET_ITEM(actions_obj, 3));
|
||||
(*actions).push_back(Object::New<MaterialAction, ImpactSoundMaterialAction>(
|
||||
sounds, target_impulse, volume));
|
||||
} else if (type == "skid_sound") {
|
||||
if (size != 4) {
|
||||
throw Exception("Expected 4 values for skid_sound action tuple.",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
Sound* sound = Python::GetPySound(PyTuple_GET_ITEM(actions_obj, 1));
|
||||
float target_impulse = Python::GetPyFloat(PyTuple_GET_ITEM(actions_obj, 2));
|
||||
float volume = Python::GetPyFloat(PyTuple_GET_ITEM(actions_obj, 3));
|
||||
(*actions).push_back(Object::New<MaterialAction, SkidSoundMaterialAction>(
|
||||
sound, target_impulse, volume));
|
||||
} else if (type == "roll_sound") {
|
||||
if (size != 4) {
|
||||
throw Exception("Expected 4 values for roll_sound action tuple.",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
Sound* sound = Python::GetPySound(PyTuple_GET_ITEM(actions_obj, 1));
|
||||
float target_impulse = Python::GetPyFloat(PyTuple_GET_ITEM(actions_obj, 2));
|
||||
float volume = Python::GetPyFloat(PyTuple_GET_ITEM(actions_obj, 3));
|
||||
(*actions).push_back(Object::New<MaterialAction, RollSoundMaterialAction>(
|
||||
sound, target_impulse, volume));
|
||||
} else {
|
||||
throw Exception("Invalid action type: '" + type + "'.", PyExcType::kValue);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
} // namespace ballistica
|
||||
46
src/ballistica/python/class/python_class_material.h
Normal file
46
src/ballistica/python/class/python_class_material.h
Normal file
@ -0,0 +1,46 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_MATERIAL_H_
|
||||
#define BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_MATERIAL_H_
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/python/class/python_class.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class PythonClassMaterial : public PythonClass {
|
||||
public:
|
||||
static auto type_name() -> const char* { return "Material"; }
|
||||
static void SetupType(PyTypeObject* obj);
|
||||
static auto Check(PyObject* o) -> bool {
|
||||
return PyObject_TypeCheck(o, &type_obj);
|
||||
}
|
||||
static PyTypeObject type_obj;
|
||||
|
||||
auto GetMaterial(bool doraise = true) const -> Material* {
|
||||
Material* m = material_->get();
|
||||
if ((!m) && doraise) throw Exception("Invalid Material");
|
||||
return m;
|
||||
}
|
||||
|
||||
private:
|
||||
static bool s_create_empty_;
|
||||
static PyMethodDef tp_methods[];
|
||||
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds)
|
||||
-> PyObject*;
|
||||
static void Delete(Object::Ref<Material>* m);
|
||||
static void tp_dealloc(PythonClassMaterial* self);
|
||||
static auto tp_getattro(PythonClassMaterial* self, PyObject* attr)
|
||||
-> PyObject*;
|
||||
static auto tp_setattro(PythonClassMaterial* self, PyObject* attr,
|
||||
PyObject* val) -> int;
|
||||
static auto tp_repr(PythonClassMaterial* self) -> PyObject*;
|
||||
static auto AddActions(PythonClassMaterial* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject*;
|
||||
static auto Dir(PythonClassMaterial* self) -> PyObject*;
|
||||
Object::Ref<Material>* material_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_MATERIAL_H_
|
||||
109
src/ballistica/python/class/python_class_model.cc
Normal file
109
src/ballistica/python/class/python_class_model.cc
Normal file
@ -0,0 +1,109 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/class/python_class_model.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/game/game.h"
|
||||
#include "ballistica/media/component/model.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
auto PythonClassModel::tp_repr(PythonClassModel* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Object::Ref<Model> m = *(self->model_);
|
||||
return Py_BuildValue(
|
||||
"s", (std::string("<ba.Model ")
|
||||
+ (m.exists() ? ("\"" + m->name() + "\"") : "(empty ref)") + ">")
|
||||
.c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
void PythonClassModel::SetupType(PyTypeObject* obj) {
|
||||
PythonClass::SetupType(obj);
|
||||
obj->tp_name = "ba.Model";
|
||||
obj->tp_basicsize = sizeof(PythonClassModel);
|
||||
obj->tp_doc =
|
||||
"A reference to a model.\n"
|
||||
"\n"
|
||||
"Category: Asset Classes\n"
|
||||
"\n"
|
||||
"Models are used for drawing.\n"
|
||||
"Use ba.getmodel() to instantiate one.";
|
||||
obj->tp_repr = (reprfunc)tp_repr;
|
||||
obj->tp_new = tp_new;
|
||||
obj->tp_dealloc = (destructor)tp_dealloc;
|
||||
}
|
||||
|
||||
auto PythonClassModel::Create(Model* model) -> PyObject* {
|
||||
s_create_empty_ = true; // prevent class from erroring on create
|
||||
auto* t = reinterpret_cast<PythonClassModel*>(
|
||||
PyObject_CallObject(reinterpret_cast<PyObject*>(&type_obj), nullptr));
|
||||
s_create_empty_ = false;
|
||||
if (!t) {
|
||||
throw Exception("ba.Model creation failed.");
|
||||
}
|
||||
*(t->model_) = model;
|
||||
return reinterpret_cast<PyObject*>(t);
|
||||
}
|
||||
|
||||
auto PythonClassModel::GetModel(bool doraise) const -> Model* {
|
||||
Model* model = model_->get();
|
||||
if (!model && doraise) {
|
||||
throw Exception("Invalid Model.", PyExcType::kNotFound);
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
auto PythonClassModel::tp_new(PyTypeObject* type, PyObject* args,
|
||||
PyObject* kwds) -> PyObject* {
|
||||
auto* self = reinterpret_cast<PythonClassModel*>(type->tp_alloc(type, 0));
|
||||
if (self) {
|
||||
BA_PYTHON_TRY;
|
||||
if (!InGameThread()) {
|
||||
throw Exception(
|
||||
"ERROR: " + std::string(type_obj.tp_name)
|
||||
+ " objects must only be created in the game thread (current is ("
|
||||
+ GetCurrentThreadName() + ").");
|
||||
}
|
||||
if (!s_create_empty_) {
|
||||
throw Exception(
|
||||
"Can't instantiate Models directly; use ba.getmodel() to get "
|
||||
"them.");
|
||||
}
|
||||
self->model_ = new Object::Ref<Model>();
|
||||
BA_PYTHON_NEW_CATCH;
|
||||
}
|
||||
return reinterpret_cast<PyObject*>(self);
|
||||
}
|
||||
|
||||
void PythonClassModel::Delete(Object::Ref<Model>* ref) {
|
||||
assert(InGameThread());
|
||||
|
||||
// if we're the py-object for a model, clear them out
|
||||
// (FIXME - we should pass the old pointer in here to sanity-test that we
|
||||
// were their ref)
|
||||
if (ref->exists()) {
|
||||
(*ref)->ClearPyObject();
|
||||
}
|
||||
delete ref;
|
||||
}
|
||||
|
||||
void PythonClassModel::tp_dealloc(PythonClassModel* self) {
|
||||
BA_PYTHON_TRY;
|
||||
// these have to be deleted in the game thread - send the ptr along if need
|
||||
// be; otherwise do it immediately
|
||||
if (!InGameThread()) {
|
||||
Object::Ref<Model>* m = self->model_;
|
||||
g_game->PushCall([m] { Delete(m); });
|
||||
} else {
|
||||
Delete(self->model_);
|
||||
}
|
||||
BA_PYTHON_DEALLOC_CATCH;
|
||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
||||
}
|
||||
|
||||
bool PythonClassModel::s_create_empty_ = false;
|
||||
PyTypeObject PythonClassModel::type_obj;
|
||||
|
||||
} // namespace ballistica
|
||||
34
src/ballistica/python/class/python_class_model.h
Normal file
34
src/ballistica/python/class/python_class_model.h
Normal file
@ -0,0 +1,34 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_MODEL_H_
|
||||
#define BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_MODEL_H_
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/python/class/python_class.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class PythonClassModel : public PythonClass {
|
||||
public:
|
||||
static auto type_name() -> const char* { return "Model"; }
|
||||
static auto tp_repr(PythonClassModel* self) -> PyObject*;
|
||||
static void SetupType(PyTypeObject* obj);
|
||||
static PyTypeObject type_obj;
|
||||
static auto Create(Model* model) -> PyObject*;
|
||||
static auto Check(PyObject* o) -> bool {
|
||||
return PyObject_TypeCheck(o, &type_obj);
|
||||
}
|
||||
auto GetModel(bool doraise = true) const -> Model*;
|
||||
|
||||
private:
|
||||
static bool s_create_empty_;
|
||||
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* kwds)
|
||||
-> PyObject*;
|
||||
static void tp_dealloc(PythonClassModel* self);
|
||||
static void Delete(Object::Ref<Model>* ref);
|
||||
Object::Ref<Model>* model_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_MODEL_H_
|
||||
458
src/ballistica/python/class/python_class_node.cc
Normal file
458
src/ballistica/python/class/python_class_node.cc
Normal file
@ -0,0 +1,458 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/class/python_class_node.h"
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/game/game_stream.h"
|
||||
#include "ballistica/python/python.h"
|
||||
#include "ballistica/scene/scene.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Ignore a few things that python macros do.
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "hicpp-signed-bitwise"
|
||||
#pragma ide diagnostic ignored "RedundantCast"
|
||||
|
||||
PyNumberMethods PythonClassNode::as_number_;
|
||||
|
||||
void PythonClassNode::SetupType(PyTypeObject* obj) {
|
||||
PythonClass::SetupType(obj);
|
||||
obj->tp_repr = (reprfunc)tp_repr;
|
||||
obj->tp_name = "ba.Node";
|
||||
obj->tp_basicsize = sizeof(PythonClassNode);
|
||||
obj->tp_doc =
|
||||
"Reference to a Node; the low level building block of the game.\n"
|
||||
"\n"
|
||||
"Category: Gameplay Classes\n"
|
||||
"\n"
|
||||
"At its core, a game is nothing more than a scene of Nodes\n"
|
||||
"with attributes getting interconnected or set over time.\n"
|
||||
"\n"
|
||||
"A ba.Node instance should be thought of as a weak-reference\n"
|
||||
"to a game node; *not* the node itself. This means a Node's\n"
|
||||
"lifecycle is completely independent of how many Python references\n"
|
||||
"to it exist. To explicitly add a new node to the game, use\n"
|
||||
"ba.newnode(), and to explicitly delete one, use ba.Node.delete().\n"
|
||||
"ba.Node.exists() can be used to determine if a Node still points to\n"
|
||||
"a live node in the game.\n"
|
||||
"\n"
|
||||
"You can use ba.Node(None) to instantiate an invalid\n"
|
||||
"Node reference (sometimes used as attr values/etc).";
|
||||
obj->tp_new = tp_new;
|
||||
obj->tp_dealloc = (destructor)tp_dealloc;
|
||||
obj->tp_getattro = (getattrofunc)tp_getattro;
|
||||
obj->tp_setattro = (setattrofunc)tp_setattro;
|
||||
obj->tp_methods = tp_methods;
|
||||
|
||||
// We provide number methods only for bool functionality.
|
||||
memset(&as_number_, 0, sizeof(as_number_));
|
||||
as_number_.nb_bool = (inquiry)nb_bool;
|
||||
obj->tp_as_number = &as_number_;
|
||||
}
|
||||
|
||||
auto PythonClassNode::Create(Node* node) -> PyObject* {
|
||||
// Make sure we only have one python ref per node.
|
||||
if (node) {
|
||||
assert(!node->has_py_ref());
|
||||
}
|
||||
|
||||
s_create_empty_ = true; // Prevent class from erroring on create.
|
||||
auto* py_node = reinterpret_cast<PythonClassNode*>(
|
||||
PyObject_CallObject(reinterpret_cast<PyObject*>(&type_obj), nullptr));
|
||||
s_create_empty_ = false;
|
||||
if (!py_node) {
|
||||
throw Exception("ba.Node creation failed.");
|
||||
}
|
||||
|
||||
*(py_node->node_) = node;
|
||||
return reinterpret_cast<PyObject*>(py_node);
|
||||
}
|
||||
|
||||
auto PythonClassNode::GetNode(bool doraise) const -> Node* {
|
||||
Node* n = node_->get();
|
||||
if (!n && doraise) {
|
||||
throw Exception(PyExcType::kNodeNotFound);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
auto PythonClassNode::tp_new(PyTypeObject* type, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
auto* self = reinterpret_cast<PythonClassNode*>(type->tp_alloc(type, 0));
|
||||
if (self) {
|
||||
BA_PYTHON_TRY;
|
||||
if (!InGameThread()) {
|
||||
throw Exception(
|
||||
"ERROR: " + std::string(type_obj.tp_name)
|
||||
+ " objects must only be created in the game thread (current is ("
|
||||
+ GetCurrentThreadName() + ").");
|
||||
}
|
||||
if (!s_create_empty_) {
|
||||
if (!PyTuple_Check(args) || (PyTuple_GET_SIZE(args) != 1)
|
||||
|| (keywds != nullptr) || (PyTuple_GET_ITEM(args, 0) != Py_None)) {
|
||||
throw Exception(
|
||||
"Can't create Nodes this way; use ba.newnode() or use "
|
||||
"ba.Node(None) to get an invalid reference.");
|
||||
}
|
||||
}
|
||||
self->node_ = new Object::WeakRef<Node>();
|
||||
BA_PYTHON_NEW_CATCH;
|
||||
}
|
||||
return reinterpret_cast<PyObject*>(self);
|
||||
}
|
||||
|
||||
void PythonClassNode::tp_dealloc(PythonClassNode* self) {
|
||||
BA_PYTHON_TRY;
|
||||
// These have to be deleted in the game thread; send the ptr along if need
|
||||
// be; otherwise do it immediately.
|
||||
if (!InGameThread()) {
|
||||
Object::WeakRef<Node>* n = self->node_;
|
||||
g_game->PushCall([n] { delete n; });
|
||||
} else {
|
||||
delete self->node_;
|
||||
}
|
||||
BA_PYTHON_DEALLOC_CATCH;
|
||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
||||
}
|
||||
|
||||
auto PythonClassNode::tp_repr(PythonClassNode* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Node* node = self->node_->get();
|
||||
return Py_BuildValue(
|
||||
"s",
|
||||
std::string("<ba.Node "
|
||||
+ (node ? ("#" + std::to_string(node->id()) + " ") : "")
|
||||
+ (node ? ("'" + node->label() + "'") : "(empty ref)") + ">")
|
||||
.c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassNode::tp_getattro(PythonClassNode* self, PyObject* attr)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
// Do we need to support other attr types?
|
||||
assert(PyUnicode_Check(attr));
|
||||
|
||||
// If our node exists and has this attr, return it.
|
||||
// Otherwise do default python path.
|
||||
Node* node = self->node_->get();
|
||||
const char* attr_name = PyUnicode_AsUTF8(attr);
|
||||
if (node && node->HasAttribute(attr_name)) {
|
||||
return Python::GetNodeAttr(node, attr_name);
|
||||
} else {
|
||||
return PyObject_GenericGetAttr(reinterpret_cast<PyObject*>(self), attr);
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassNode::Exists(PythonClassNode* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
if (self->node_->exists()) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassNode::GetNodeType(PythonClassNode* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
Node* node = self->node_->get();
|
||||
if (!node) {
|
||||
throw Exception(PyExcType::kNodeNotFound);
|
||||
}
|
||||
return PyUnicode_FromString(node->type()->name().c_str());
|
||||
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassNode::GetName(PythonClassNode* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
Node* node = self->node_->get();
|
||||
if (!node) {
|
||||
throw Exception(PyExcType::kNodeNotFound);
|
||||
}
|
||||
return PyUnicode_FromString(node->label().c_str());
|
||||
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassNode::GetDelegate(PythonClassNode* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
static const char* kwlist[] = {"type", "doraise", nullptr};
|
||||
PyObject* type_obj{};
|
||||
int doraise{};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|p",
|
||||
const_cast<char**>(kwlist), &type_obj,
|
||||
&doraise)) {
|
||||
return nullptr;
|
||||
}
|
||||
Node* node = self->node_->get();
|
||||
if (!node) {
|
||||
throw Exception(PyExcType::kNodeNotFound);
|
||||
}
|
||||
if (!PyType_Check(type_obj)) {
|
||||
throw Exception("Passed type arg is not a type.", PyExcType::kType);
|
||||
}
|
||||
if (PyObject* obj = node->GetDelegate()) {
|
||||
int isinst = PyObject_IsInstance(obj, type_obj);
|
||||
if (isinst == -1) {
|
||||
return nullptr;
|
||||
}
|
||||
if (isinst) {
|
||||
Py_INCREF(obj);
|
||||
return obj;
|
||||
} else {
|
||||
if (doraise) {
|
||||
throw Exception("Requested delegate type not found on '"
|
||||
+ node->type()->name()
|
||||
+ "' node. (type=" + Python::ObjToString(type_obj)
|
||||
+ ", delegate=" + Python::ObjToString(obj) + ")",
|
||||
PyExcType::kDelegateNotFound);
|
||||
}
|
||||
}
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassNode::Delete(PythonClassNode* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
int ignore_missing = 1;
|
||||
static const char* kwlist[] = {"ignore_missing", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(
|
||||
args, keywds, "|i", const_cast<char**>(kwlist), &ignore_missing)) {
|
||||
return nullptr;
|
||||
}
|
||||
Node* node = self->node_->get();
|
||||
if (!node) {
|
||||
if (!ignore_missing) {
|
||||
throw Exception(PyExcType::kNodeNotFound);
|
||||
}
|
||||
} else {
|
||||
node->scene()->DeleteNode(node);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassNode::HandleMessage(PythonClassNode* self, PyObject* args)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Py_ssize_t tuple_size = PyTuple_GET_SIZE(args);
|
||||
if (tuple_size < 1) {
|
||||
PyErr_SetString(PyExc_AttributeError, "must provide at least 1 arg");
|
||||
return nullptr;
|
||||
}
|
||||
Buffer<char> b;
|
||||
PyObject* user_message_obj;
|
||||
Python::DoBuildNodeMessage(args, 0, &b, &user_message_obj);
|
||||
|
||||
// Should we fail if the node doesn't exist??
|
||||
Node* node = self->node_->get();
|
||||
if (node) {
|
||||
HostActivity* host_activity = node->context().GetHostActivity();
|
||||
if (!host_activity) {
|
||||
throw Exception("Invalid context.", PyExcType::kContext);
|
||||
}
|
||||
// For user messages we pass them directly to the node
|
||||
// since by their nature they don't go out over the network and are just
|
||||
// for use within the scripting system.
|
||||
if (user_message_obj) {
|
||||
node->DispatchUserMessage(user_message_obj, "Node User-Message dispatch");
|
||||
} else {
|
||||
if (GameStream* output_stream = node->scene()->GetGameStream()) {
|
||||
output_stream->NodeMessage(node, b.data(), b.size());
|
||||
}
|
||||
node->DispatchNodeMessage(b.data());
|
||||
}
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassNode::AddDeathAction(PythonClassNode* self, PyObject* args)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
PyObject* call_obj;
|
||||
if (!PyArg_ParseTuple(args, "O", &call_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
Node* n = self->node_->get();
|
||||
if (!n) {
|
||||
throw Exception(PyExcType::kNodeNotFound);
|
||||
}
|
||||
|
||||
// We don't have to go through a host-activity but lets make sure we're in
|
||||
// one.
|
||||
HostActivity* host_activity = n->context().GetHostActivity();
|
||||
if (!host_activity) {
|
||||
throw Exception("Invalid context.", PyExcType::kContext);
|
||||
}
|
||||
n->AddNodeDeathAction(call_obj);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassNode::ConnectAttr(PythonClassNode* self, PyObject* args)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
PyObject* dst_node_obj;
|
||||
Node* node = self->node_->get();
|
||||
if (!node) {
|
||||
throw Exception(PyExcType::kNodeNotFound);
|
||||
}
|
||||
char *src_attr_name, *dst_attr_name;
|
||||
if (!PyArg_ParseTuple(args, "sOs", &src_attr_name, &dst_node_obj,
|
||||
&dst_attr_name)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Allow dead-refs and None.
|
||||
Node* dst_node = Python::GetPyNode(dst_node_obj, true, true);
|
||||
if (!dst_node) {
|
||||
throw Exception(PyExcType::kNodeNotFound);
|
||||
}
|
||||
NodeAttributeUnbound* src_attr =
|
||||
node->type()->GetAttribute(std::string(src_attr_name));
|
||||
NodeAttributeUnbound* dst_attr =
|
||||
dst_node->type()->GetAttribute(std::string(dst_attr_name));
|
||||
|
||||
// Push to output_stream first to catch scene mismatch errors.
|
||||
if (GameStream* output_stream = node->scene()->GetGameStream()) {
|
||||
output_stream->ConnectNodeAttribute(node, src_attr, dst_node, dst_attr);
|
||||
}
|
||||
|
||||
// Now apply locally.
|
||||
node->ConnectAttribute(src_attr, dst_node, dst_attr);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassNode::Dir(PythonClassNode* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
// Start with the standard python dir listing.
|
||||
PyObject* dir_list = Python::generic_dir(reinterpret_cast<PyObject*>(self));
|
||||
assert(PyList_Check(dir_list));
|
||||
|
||||
// ..now grab all this guy's BA attributes and add them in.
|
||||
Node* node = self->node_->get();
|
||||
if (node) {
|
||||
std::list<std::string> attrs;
|
||||
node->ListAttributes(&attrs);
|
||||
for (auto& attr : attrs) {
|
||||
PyList_Append(dir_list, PythonRef(PyUnicode_FromString(attr.c_str()),
|
||||
PythonRef::kSteal)
|
||||
.get());
|
||||
}
|
||||
}
|
||||
PyList_Sort(dir_list);
|
||||
return dir_list;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassNode::nb_bool(PythonClassNode* self) -> int {
|
||||
return self->node_->exists();
|
||||
}
|
||||
|
||||
auto PythonClassNode::tp_setattro(PythonClassNode* self, PyObject* attr,
|
||||
PyObject* val) -> int {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
// FIXME: do we need to support other attr types?
|
||||
assert(PyUnicode_Check(attr));
|
||||
Node* n = self->node_->get();
|
||||
if (!n) {
|
||||
throw Exception(PyExcType::kNodeNotFound);
|
||||
}
|
||||
Python::SetNodeAttr(n, PyUnicode_AsUTF8(attr), val);
|
||||
return 0;
|
||||
BA_PYTHON_INT_CATCH;
|
||||
}
|
||||
|
||||
PyMethodDef PythonClassNode::tp_methods[] = {
|
||||
{"exists", (PyCFunction)Exists, METH_NOARGS,
|
||||
"exists() -> bool\n"
|
||||
"\n"
|
||||
"Returns whether the Node still exists.\n"
|
||||
"Most functionality will fail on a nonexistent Node, so it's never a bad\n"
|
||||
"idea to check this.\n"
|
||||
"\n"
|
||||
"Note that you can also use the boolean operator for this same\n"
|
||||
"functionality, so a statement such as \"if mynode\" will do\n"
|
||||
"the right thing both for Node objects and values of None."},
|
||||
{"getnodetype", (PyCFunction)GetNodeType, METH_NOARGS,
|
||||
"getnodetype() -> str\n"
|
||||
"\n"
|
||||
"Return the type of Node referenced by this object as a string.\n"
|
||||
"(Note this is different from the Python type which is always ba.Node)"},
|
||||
{"getname", (PyCFunction)GetName, METH_NOARGS,
|
||||
"getname() -> str\n"
|
||||
"\n"
|
||||
"Return the name assigned to a Node; used mainly for debugging"},
|
||||
{"getdelegate", (PyCFunction)GetDelegate, METH_VARARGS | METH_KEYWORDS,
|
||||
"getdelegate(type: Type, doraise: bool = False) -> <varies>\n"
|
||||
"\n"
|
||||
"Return the node's current delegate object if it matches a certain type.\n"
|
||||
"\n"
|
||||
"If the node has no delegate or it is not an instance of the passed\n"
|
||||
"type, then None will be returned. If 'doraise' is True, then an\n"
|
||||
"ba.DelegateNotFoundError will be raised instead."},
|
||||
{"delete", (PyCFunction)Delete, METH_VARARGS | METH_KEYWORDS,
|
||||
"delete(ignore_missing: bool = True) -> None\n"
|
||||
"\n"
|
||||
"Delete the node. Ignores already-deleted nodes if ignore_missing\n"
|
||||
"is True; otherwise a ba.NodeNotFoundError is thrown."},
|
||||
{"handlemessage", (PyCFunction)HandleMessage, METH_VARARGS,
|
||||
"handlemessage(*args: Any) -> None\n"
|
||||
"\n"
|
||||
"General message handling; can be passed any message object.\n"
|
||||
"\n"
|
||||
"All standard message objects are forwarded along to the ba.Node's\n"
|
||||
"delegate for handling (generally the ba.Actor that made the node).\n"
|
||||
"\n"
|
||||
"ba.Nodes are unique, however, in that they can be passed a second\n"
|
||||
"form of message; 'node-messages'. These consist of a string type-name\n"
|
||||
"as a first argument along with the args specific to that type name\n"
|
||||
"as additional arguments.\n"
|
||||
"Node-messages communicate directly with the low-level node layer\n"
|
||||
"and are delivered simultaneously on all game clients,\n"
|
||||
"acting as an alternative to setting node attributes."},
|
||||
{"add_death_action", (PyCFunction)AddDeathAction, METH_VARARGS,
|
||||
"add_death_action(action: Callable[[], None]) -> None\n"
|
||||
"\n"
|
||||
"Add a callable object to be called upon this node's death.\n"
|
||||
"Note that these actions are run just after the node dies, not before.\n"},
|
||||
{"connectattr", (PyCFunction)ConnectAttr, METH_VARARGS,
|
||||
"connectattr(srcattr: str, dstnode: Node, dstattr: str) -> None\n"
|
||||
"\n"
|
||||
"Connect one of this node's attributes to an attribute on another node.\n"
|
||||
"This will immediately set the target attribute's value to that of the\n"
|
||||
"source attribute, and will continue to do so once per step as long as\n"
|
||||
"the two nodes exist. The connection can be severed by setting the\n"
|
||||
"target attribute to any value or connecting another node attribute\n"
|
||||
"to it.\n"
|
||||
"\n"
|
||||
"# Example: create a locator and attach a light to it:\n"
|
||||
"light = ba.newnode('light')\n"
|
||||
"loc = ba.newnode('locator', attrs={'position': (0,10,0)})\n"
|
||||
"loc.connectattr('position', light, 'position')"},
|
||||
{"__dir__", (PyCFunction)Dir, METH_NOARGS,
|
||||
"allows inclusion of our custom attrs in standard python dir()"},
|
||||
{nullptr}};
|
||||
|
||||
bool PythonClassNode::s_create_empty_ = false;
|
||||
PyTypeObject PythonClassNode::type_obj;
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
} // namespace ballistica
|
||||
52
src/ballistica/python/class/python_class_node.h
Normal file
52
src/ballistica/python/class/python_class_node.h
Normal file
@ -0,0 +1,52 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_NODE_H_
|
||||
#define BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_NODE_H_
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/python/class/python_class.h"
|
||||
#include "ballistica/scene/node/node_type.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class PythonClassNode : public PythonClass {
|
||||
public:
|
||||
static auto type_name() -> const char* { return "Node"; }
|
||||
static void SetupType(PyTypeObject* obj);
|
||||
static auto Create(Node* node) -> PyObject*;
|
||||
static auto Check(PyObject* o) -> bool {
|
||||
return PyObject_TypeCheck(o, &type_obj);
|
||||
}
|
||||
static PyTypeObject type_obj;
|
||||
auto GetNode(bool doraise = true) const -> Node*;
|
||||
|
||||
private:
|
||||
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds)
|
||||
-> PyObject*;
|
||||
static void tp_dealloc(PythonClassNode* self);
|
||||
static auto tp_repr(PythonClassNode* self) -> PyObject*;
|
||||
static auto tp_getattro(PythonClassNode* self, PyObject* attr) -> PyObject*;
|
||||
static auto tp_setattro(PythonClassNode* self, PyObject* attr, PyObject* val)
|
||||
-> int;
|
||||
static auto Exists(PythonClassNode* self) -> PyObject*;
|
||||
static auto GetNodeType(PythonClassNode* self) -> PyObject*;
|
||||
static auto GetName(PythonClassNode* self) -> PyObject*;
|
||||
static auto GetDelegate(PythonClassNode* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject*;
|
||||
static auto Delete(PythonClassNode* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject*;
|
||||
static auto HandleMessage(PythonClassNode* self, PyObject* args) -> PyObject*;
|
||||
static auto AddDeathAction(PythonClassNode* self, PyObject* args)
|
||||
-> PyObject*;
|
||||
static auto ConnectAttr(PythonClassNode* self, PyObject* args) -> PyObject*;
|
||||
static auto Dir(PythonClassNode* self) -> PyObject*;
|
||||
static auto nb_bool(PythonClassNode* self) -> int;
|
||||
static bool s_create_empty_;
|
||||
static PyMethodDef tp_methods[];
|
||||
Object::WeakRef<Node>* node_;
|
||||
static PyNumberMethods as_number_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_NODE_H_
|
||||
114
src/ballistica/python/class/python_class_session_data.cc
Normal file
114
src/ballistica/python/class/python_class_session_data.cc
Normal file
@ -0,0 +1,114 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/class/python_class_session_data.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/game/game.h"
|
||||
#include "ballistica/game/session/session.h"
|
||||
#include "ballistica/generic/utils.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
auto PythonClassSessionData::nb_bool(PythonClassSessionData* self) -> int {
|
||||
return self->session_->exists();
|
||||
}
|
||||
|
||||
PyNumberMethods PythonClassSessionData::as_number_;
|
||||
|
||||
void PythonClassSessionData::SetupType(PyTypeObject* obj) {
|
||||
PythonClass::SetupType(obj);
|
||||
obj->tp_name = "_ba.SessionData";
|
||||
obj->tp_basicsize = sizeof(PythonClassSessionData);
|
||||
obj->tp_doc = "(internal)";
|
||||
obj->tp_new = tp_new;
|
||||
obj->tp_dealloc = (destructor)tp_dealloc;
|
||||
obj->tp_repr = (reprfunc)tp_repr;
|
||||
obj->tp_methods = tp_methods;
|
||||
|
||||
// We provide number methods only for bool functionality.
|
||||
memset(&as_number_, 0, sizeof(as_number_));
|
||||
as_number_.nb_bool = (inquiry)nb_bool;
|
||||
obj->tp_as_number = &as_number_;
|
||||
}
|
||||
|
||||
auto PythonClassSessionData::Create(Session* session) -> PyObject* {
|
||||
auto* py_session_data = reinterpret_cast<PythonClassSessionData*>(
|
||||
PyObject_CallObject(reinterpret_cast<PyObject*>(&type_obj), nullptr));
|
||||
BA_PRECONDITION(py_session_data);
|
||||
*(py_session_data->session_) = session;
|
||||
return reinterpret_cast<PyObject*>(py_session_data);
|
||||
}
|
||||
|
||||
auto PythonClassSessionData::GetSession() const -> Session* {
|
||||
Session* session = session_->get();
|
||||
if (!session) {
|
||||
throw Exception("Invalid SessionData.", PyExcType::kSessionNotFound);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
auto PythonClassSessionData::tp_repr(PythonClassSessionData* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
return Py_BuildValue("s", (std::string("<Ballistica SessionData ")
|
||||
+ Utils::PtrToString(self->session_->get()) + " >")
|
||||
.c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassSessionData::tp_new(PyTypeObject* type, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
auto* self =
|
||||
reinterpret_cast<PythonClassSessionData*>(type->tp_alloc(type, 0));
|
||||
if (self) {
|
||||
BA_PYTHON_TRY;
|
||||
if (!InGameThread()) {
|
||||
throw Exception(
|
||||
"ERROR: " + std::string(type_obj.tp_name)
|
||||
+ " objects must only be created in the game thread (current is ("
|
||||
+ GetCurrentThreadName() + ").");
|
||||
}
|
||||
self->session_ = new Object::WeakRef<Session>();
|
||||
BA_PYTHON_NEW_CATCH;
|
||||
}
|
||||
return reinterpret_cast<PyObject*>(self);
|
||||
}
|
||||
|
||||
void PythonClassSessionData::tp_dealloc(PythonClassSessionData* self) {
|
||||
BA_PYTHON_TRY;
|
||||
// These have to be deleted in the game thread;
|
||||
// ...send the ptr along if need be.
|
||||
// FIXME: technically the main thread has a pointer to a dead PyObject
|
||||
// until the delete goes through; could that ever be a problem?
|
||||
if (!InGameThread()) {
|
||||
Object::WeakRef<Session>* s = self->session_;
|
||||
g_game->PushCall([s] { delete s; });
|
||||
} else {
|
||||
delete self->session_;
|
||||
}
|
||||
BA_PYTHON_DEALLOC_CATCH;
|
||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
||||
}
|
||||
|
||||
auto PythonClassSessionData::Exists(PythonClassSessionData* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Session* sgc = self->session_->get();
|
||||
if (sgc) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
PyTypeObject PythonClassSessionData::type_obj;
|
||||
PyMethodDef PythonClassSessionData::tp_methods[] = {
|
||||
{"exists", (PyCFunction)Exists, METH_NOARGS,
|
||||
"exists() -> bool\n"
|
||||
"\n"
|
||||
"Returns whether the SessionData still exists.\n"
|
||||
"Most functionality will fail on a nonexistent instance."},
|
||||
{nullptr}};
|
||||
|
||||
} // namespace ballistica
|
||||
36
src/ballistica/python/class/python_class_session_data.h
Normal file
36
src/ballistica/python/class/python_class_session_data.h
Normal file
@ -0,0 +1,36 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_SESSION_DATA_H_
|
||||
#define BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_SESSION_DATA_H_
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/python/class/python_class.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class PythonClassSessionData : public PythonClass {
|
||||
public:
|
||||
static auto type_name() -> const char* { return "SessionData"; }
|
||||
static void SetupType(PyTypeObject* obj);
|
||||
static auto Create(Session* session) -> PyObject*;
|
||||
static auto Check(PyObject* o) -> bool {
|
||||
return PyObject_TypeCheck(o, &type_obj);
|
||||
}
|
||||
static PyTypeObject type_obj;
|
||||
auto GetSession() const -> Session*;
|
||||
|
||||
private:
|
||||
static PyMethodDef tp_methods[];
|
||||
static auto tp_repr(PythonClassSessionData* self) -> PyObject*;
|
||||
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds)
|
||||
-> PyObject*;
|
||||
static void tp_dealloc(PythonClassSessionData* self);
|
||||
static auto Exists(PythonClassSessionData* self) -> PyObject*;
|
||||
Object::WeakRef<Session>* session_;
|
||||
static auto nb_bool(PythonClassSessionData* self) -> int;
|
||||
static PyNumberMethods as_number_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_SESSION_DATA_H_
|
||||
746
src/ballistica/python/class/python_class_session_player.cc
Normal file
746
src/ballistica/python/class/python_class_session_player.cc
Normal file
@ -0,0 +1,746 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/class/python_class_session_player.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/game/host_activity.h"
|
||||
#include "ballistica/game/player.h"
|
||||
#include "ballistica/game/session/host_session.h"
|
||||
#include "ballistica/input/device/input_device.h"
|
||||
#include "ballistica/python/python.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Ignore signed bitwise stuff; python macros do it quite a bit.
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "hicpp-signed-bitwise"
|
||||
#pragma ide diagnostic ignored "RedundantCast"
|
||||
|
||||
auto PythonClassSessionPlayer::nb_bool(PythonClassSessionPlayer* self) -> int {
|
||||
return self->player_->exists();
|
||||
}
|
||||
|
||||
PyNumberMethods PythonClassSessionPlayer::as_number_;
|
||||
|
||||
// Attrs we expose through our custom getattr/setattr.
|
||||
#define ATTR_IN_GAME "in_game"
|
||||
#define ATTR_SESSIONTEAM "sessionteam"
|
||||
#define ATTR_COLOR "color"
|
||||
#define ATTR_HIGHLIGHT "highlight"
|
||||
#define ATTR_CHARACTER "character"
|
||||
#define ATTR_ACTIVITYPLAYER "activityplayer"
|
||||
#define ATTR_ID "id"
|
||||
#define ATTR_INPUT_DEVICE "inputdevice"
|
||||
|
||||
// The set we expose via dir().
|
||||
static const char* extra_dir_attrs[] = {
|
||||
ATTR_ID, ATTR_IN_GAME, ATTR_SESSIONTEAM, ATTR_COLOR,
|
||||
ATTR_HIGHLIGHT, ATTR_CHARACTER, ATTR_INPUT_DEVICE, nullptr};
|
||||
|
||||
void PythonClassSessionPlayer::SetupType(PyTypeObject* obj) {
|
||||
PythonClass::SetupType(obj);
|
||||
obj->tp_name = "ba.SessionPlayer";
|
||||
obj->tp_basicsize = sizeof(PythonClassSessionPlayer);
|
||||
|
||||
// clang-format off
|
||||
|
||||
obj->tp_doc =
|
||||
"A reference to a player in the ba.Session.\n"
|
||||
"\n"
|
||||
"Category: Gameplay Classes\n"
|
||||
"\n"
|
||||
"These are created and managed internally and\n"
|
||||
"provided to your Session/Activity instances.\n"
|
||||
"Be aware that, like ba.Nodes, ba.SessionPlayer objects are 'weak'\n"
|
||||
"references under-the-hood; a player can leave the game at\n"
|
||||
" any point. For this reason, you should make judicious use of the\n"
|
||||
"ba.SessionPlayer.exists() method (or boolean operator) to ensure\n"
|
||||
"that a SessionPlayer is still present if retaining references to one\n"
|
||||
"for any length of time.\n"
|
||||
"\n"
|
||||
"Attributes:\n"
|
||||
"\n"
|
||||
" " ATTR_ID ": int\n"
|
||||
" The unique numeric ID of the Player.\n"
|
||||
"\n"
|
||||
" Note that you can also use the boolean operator for this same\n"
|
||||
" functionality, so a statement such as \"if player\" will do\n"
|
||||
" the right thing both for Player objects and values of None.\n"
|
||||
"\n"
|
||||
" " ATTR_IN_GAME ": bool\n"
|
||||
" This bool value will be True once the Player has completed\n"
|
||||
" any lobby character/team selection.\n"
|
||||
"\n"
|
||||
" " ATTR_SESSIONTEAM ": ba.SessionTeam\n"
|
||||
" The ba.SessionTeam this Player is on. If the SessionPlayer\n"
|
||||
" is still in its lobby selecting a team/etc. then a\n"
|
||||
" ba.SessionTeamNotFoundError will be raised.\n"
|
||||
"\n"
|
||||
" " ATTR_INPUT_DEVICE ": ba.InputDevice\n"
|
||||
" The input device associated with the player.\n"
|
||||
"\n"
|
||||
" " ATTR_COLOR ": Sequence[float]\n"
|
||||
" The base color for this Player.\n"
|
||||
" In team games this will match the ba.SessionTeam's color.\n"
|
||||
"\n"
|
||||
" " ATTR_HIGHLIGHT ": Sequence[float]\n"
|
||||
" A secondary color for this player.\n"
|
||||
" This is used for minor highlights and accents\n"
|
||||
" to allow a player to stand apart from his teammates\n"
|
||||
" who may all share the same team (primary) color.\n"
|
||||
"\n"
|
||||
" " ATTR_CHARACTER ": str\n"
|
||||
" The character this player has selected in their profile.\n"
|
||||
"\n"
|
||||
" " ATTR_ACTIVITYPLAYER ": Optional[ba.Player]\n"
|
||||
" The current game-specific instance for this player.\n";
|
||||
|
||||
// clang-format on
|
||||
|
||||
obj->tp_new = tp_new;
|
||||
obj->tp_repr = (reprfunc)tp_repr;
|
||||
obj->tp_methods = tp_methods;
|
||||
obj->tp_dealloc = (destructor)tp_dealloc;
|
||||
obj->tp_getattro = (getattrofunc)tp_getattro;
|
||||
obj->tp_setattro = (setattrofunc)tp_setattro;
|
||||
|
||||
// We provide number methods only for bool functionality.
|
||||
memset(&as_number_, 0, sizeof(as_number_));
|
||||
as_number_.nb_bool = (inquiry)nb_bool;
|
||||
obj->tp_as_number = &as_number_;
|
||||
}
|
||||
|
||||
auto PythonClassSessionPlayer::Create(Player* player) -> PyObject* {
|
||||
// Make sure we only have one python ref per material.
|
||||
if (player) {
|
||||
assert(!player->has_py_ref());
|
||||
}
|
||||
s_create_empty_ = true; // Prevent class from erroring on create.
|
||||
auto* py_player = reinterpret_cast<PythonClassSessionPlayer*>(
|
||||
PyObject_CallObject(reinterpret_cast<PyObject*>(&type_obj), nullptr));
|
||||
s_create_empty_ = false;
|
||||
if (!py_player) {
|
||||
throw Exception("ba.Player creation failed.");
|
||||
}
|
||||
|
||||
*(py_player->player_) = player;
|
||||
return reinterpret_cast<PyObject*>(py_player);
|
||||
}
|
||||
|
||||
auto PythonClassSessionPlayer::GetPlayer(bool doraise) const -> Player* {
|
||||
Player* player = player_->get();
|
||||
if ((!player) && doraise) {
|
||||
throw Exception("Invalid SessionPlayer.",
|
||||
PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
return player;
|
||||
}
|
||||
|
||||
auto PythonClassSessionPlayer::tp_repr(PythonClassSessionPlayer* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Player* p = self->player_->get();
|
||||
int player_id = p ? p->id() : -1;
|
||||
std::string p_name = p ? p->GetName() : "invalid";
|
||||
return Py_BuildValue("s",
|
||||
(std::string("<Ballistica SessionPlayer ")
|
||||
+ std::to_string(player_id) + " \"" + p_name + "\">")
|
||||
.c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassSessionPlayer::tp_new(PyTypeObject* type, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
auto* self =
|
||||
reinterpret_cast<PythonClassSessionPlayer*>(type->tp_alloc(type, 0));
|
||||
if (self) {
|
||||
BA_PYTHON_TRY;
|
||||
if (!InGameThread()) {
|
||||
throw Exception(
|
||||
"ERROR: " + std::string(type_obj.tp_name)
|
||||
+ " objects must only be created in the game thread (current is ("
|
||||
+ GetCurrentThreadName() + ").");
|
||||
}
|
||||
|
||||
// If the user is creating one, make sure they passed None to get an
|
||||
// invalid ref.
|
||||
if (!s_create_empty_) {
|
||||
if (!PyTuple_Check(args) || (PyTuple_GET_SIZE(args) != 1)
|
||||
|| (keywds != nullptr) || (PyTuple_GET_ITEM(args, 0) != Py_None))
|
||||
throw Exception(
|
||||
"Can't instantiate SessionPlayers. To create an invalid"
|
||||
" SessionPlayer reference, call ba.SessionPlayer(None).");
|
||||
}
|
||||
self->player_ = new Object::WeakRef<Player>();
|
||||
BA_PYTHON_NEW_CATCH;
|
||||
}
|
||||
return reinterpret_cast<PyObject*>(self);
|
||||
}
|
||||
|
||||
void PythonClassSessionPlayer::tp_dealloc(PythonClassSessionPlayer* self) {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
// These have to be deleted in the game thread - send the ptr along if need
|
||||
// be; otherwise do it immediately.
|
||||
if (!InGameThread()) {
|
||||
Object::WeakRef<Player>* p = self->player_;
|
||||
g_game->PushCall([p] { delete p; });
|
||||
} else {
|
||||
delete self->player_;
|
||||
}
|
||||
BA_PYTHON_DEALLOC_CATCH;
|
||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
||||
}
|
||||
|
||||
auto PythonClassSessionPlayer::tp_getattro(PythonClassSessionPlayer* self,
|
||||
PyObject* attr) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
assert(InGameThread());
|
||||
|
||||
// Assuming this will always be a str?
|
||||
assert(PyUnicode_Check(attr));
|
||||
|
||||
const char* s = PyUnicode_AsUTF8(attr);
|
||||
if (!strcmp(s, ATTR_IN_GAME)) {
|
||||
Player* p = self->player_->get();
|
||||
if (!p) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
|
||||
// We get placed on a team as soon as we finish in the lobby
|
||||
// so lets use that as whether we're in-game or not.
|
||||
PyObject* team = p->GetPyTeam();
|
||||
assert(team != nullptr);
|
||||
if (team == Py_None) {
|
||||
Py_RETURN_FALSE;
|
||||
} else {
|
||||
Py_RETURN_TRUE;
|
||||
}
|
||||
} else if (!strcmp(s, ATTR_ID)) {
|
||||
Player* p = self->player_->get();
|
||||
if (!p) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
return PyLong_FromLong(p->id());
|
||||
} else if (!strcmp(s, ATTR_INPUT_DEVICE)) {
|
||||
Player* player = self->player_->get();
|
||||
if (!player) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
InputDevice* input_device = player->GetInputDevice();
|
||||
if (input_device) {
|
||||
return input_device->NewPyRef();
|
||||
}
|
||||
throw Exception(PyExcType::kInputDeviceNotFound);
|
||||
} else if (!strcmp(s, ATTR_SESSIONTEAM)) {
|
||||
Player* p = self->player_->get();
|
||||
if (!p) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
PyObject* team = p->GetPyTeam();
|
||||
assert(team != nullptr);
|
||||
if (team == Py_None) {
|
||||
PyErr_SetString(
|
||||
g_python->obj(Python::ObjID::kSessionTeamNotFoundError).get(),
|
||||
"SessionTeam does not exist.");
|
||||
return nullptr;
|
||||
}
|
||||
Py_INCREF(team);
|
||||
return team;
|
||||
} else if (!strcmp(s, ATTR_CHARACTER)) {
|
||||
Player* p = self->player_->get();
|
||||
if (!p) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
if (!p->has_py_data()) {
|
||||
BA_LOG_ONCE("Error: Calling getAttr for player attr '" + std::string(s)
|
||||
+ "' without data set.");
|
||||
}
|
||||
PyObject* obj = p->GetPyCharacter();
|
||||
Py_INCREF(obj);
|
||||
return obj;
|
||||
} else if (!strcmp(s, ATTR_COLOR)) {
|
||||
Player* p = self->player_->get();
|
||||
if (!p) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
if (!p->has_py_data()) {
|
||||
BA_LOG_ONCE("Error: Calling getAttr for player attr '" + std::string(s)
|
||||
+ "' without data set.");
|
||||
}
|
||||
PyObject* obj = p->GetPyColor();
|
||||
Py_INCREF(obj);
|
||||
return obj;
|
||||
} else if (!strcmp(s, ATTR_HIGHLIGHT)) {
|
||||
Player* p = self->player_->get();
|
||||
if (!p) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
if (!p->has_py_data()) {
|
||||
BA_LOG_ONCE("Error: Calling getAttr for player attr '" + std::string(s)
|
||||
+ "' without data set.");
|
||||
}
|
||||
PyObject* obj = p->GetPyHighlight();
|
||||
Py_INCREF(obj);
|
||||
return obj;
|
||||
} else if (!strcmp(s, ATTR_ACTIVITYPLAYER)) {
|
||||
Player* p = self->player_->get();
|
||||
if (!p) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
if (!p->has_py_data()) {
|
||||
BA_LOG_ONCE("Error: Calling getAttr for player attr '" + std::string(s)
|
||||
+ "' without data set.");
|
||||
}
|
||||
PyObject* obj = p->GetPyActivityPlayer();
|
||||
Py_INCREF(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Fall back to generic behavior.
|
||||
PyObject* val;
|
||||
val = PyObject_GenericGetAttr(reinterpret_cast<PyObject*>(self), attr);
|
||||
return val;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassSessionPlayer::tp_setattro(PythonClassSessionPlayer* self,
|
||||
PyObject* attr, PyObject* val)
|
||||
-> int {
|
||||
BA_PYTHON_TRY;
|
||||
// Assuming this will always be a str?
|
||||
assert(PyUnicode_Check(attr));
|
||||
const char* s = PyUnicode_AsUTF8(attr);
|
||||
|
||||
if (!strcmp(s, ATTR_ACTIVITYPLAYER)) {
|
||||
Player* p = self->player_->get();
|
||||
if (!p) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
p->SetPyActivityPlayer(val);
|
||||
return 0;
|
||||
}
|
||||
throw Exception("Attr '" + std::string(PyUnicode_AsUTF8(attr))
|
||||
+ "' is not settable on SessionPlayer objects.",
|
||||
PyExcType::kAttribute);
|
||||
// return PyObject_GenericSetAttr(reinterpret_cast<PyObject*>(self), attr,
|
||||
// val);
|
||||
BA_PYTHON_INT_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassSessionPlayer::GetName(PythonClassSessionPlayer* self,
|
||||
PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(InGameThread());
|
||||
int full = false;
|
||||
int icon = true;
|
||||
static const char* kwlist[] = {"full", "icon", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "|pp",
|
||||
const_cast<char**>(kwlist), &full, &icon)) {
|
||||
return nullptr;
|
||||
}
|
||||
Player* p = self->player_->get();
|
||||
if (!p) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
return PyUnicode_FromString(
|
||||
p->GetName(static_cast<bool>(full), static_cast<bool>(icon)).c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassSessionPlayer::Exists(PythonClassSessionPlayer* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(InGameThread());
|
||||
if (self->player_->exists()) {
|
||||
Py_RETURN_TRUE;
|
||||
}
|
||||
Py_RETURN_FALSE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassSessionPlayer::SetName(PythonClassSessionPlayer* self,
|
||||
PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(InGameThread());
|
||||
PyObject* name_obj;
|
||||
PyObject* full_name_obj = Py_None;
|
||||
|
||||
// This should be false for temp names like <choosing player>.
|
||||
int real = 1;
|
||||
static const char* kwlist[] = {"name", "full_name", "real", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|Op",
|
||||
const_cast<char**>(kwlist), &name_obj,
|
||||
&full_name_obj, &real)) {
|
||||
return nullptr;
|
||||
}
|
||||
std::string name = Python::GetPyString(name_obj);
|
||||
std::string full_name =
|
||||
(full_name_obj == Py_None) ? name : Python::GetPyString(full_name_obj);
|
||||
Player* p = self->player_->get();
|
||||
if (!p) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
p->SetName(name, full_name, static_cast<bool>(real));
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassSessionPlayer::ResetInput(PythonClassSessionPlayer* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(InGameThread());
|
||||
Player* p = self->player_->get();
|
||||
if (!p) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
p->ResetInput();
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassSessionPlayer::AssignInputCall(PythonClassSessionPlayer* self,
|
||||
PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(InGameThread());
|
||||
PyObject* input_type_obj;
|
||||
PyObject* call_obj;
|
||||
static const char* kwlist[] = {"type", "call", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "OO",
|
||||
const_cast<char**>(kwlist), &input_type_obj,
|
||||
&call_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
Player* player = self->player_->get();
|
||||
if (!player) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
if (Python::IsPyEnum_InputType(input_type_obj)) {
|
||||
InputType input_type = Python::GetPyEnum_InputType(input_type_obj);
|
||||
player->AssignInputCall(input_type, call_obj);
|
||||
} else {
|
||||
if (!PyTuple_Check(input_type_obj)) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"Expected InputType or tuple for type arg.");
|
||||
return nullptr;
|
||||
}
|
||||
Py_ssize_t tuple_size = PyTuple_GET_SIZE(input_type_obj);
|
||||
for (Py_ssize_t i = 0; i < tuple_size; i++) {
|
||||
PyObject* obj = PyTuple_GET_ITEM(input_type_obj, i);
|
||||
if (!Python::IsPyEnum_InputType(obj)) {
|
||||
PyErr_SetString(PyExc_TypeError, "Expected tuple of InputTypes.");
|
||||
return nullptr;
|
||||
}
|
||||
InputType input_type = Python::GetPyEnum_InputType(obj);
|
||||
player->AssignInputCall(input_type, call_obj);
|
||||
}
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassSessionPlayer::RemoveFromGame(PythonClassSessionPlayer* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(InGameThread());
|
||||
Player* player = self->player_->get();
|
||||
if (!player) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
} else {
|
||||
HostSession* host_session = player->GetHostSession();
|
||||
if (!host_session) {
|
||||
throw Exception("Player's host-session not found.",
|
||||
PyExcType::kSessionNotFound);
|
||||
}
|
||||
host_session->RemovePlayer(player);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassSessionPlayer::GetTeam(PythonClassSessionPlayer* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(InGameThread());
|
||||
Player* p = self->player_->get();
|
||||
if (!p) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
PyObject* team = p->GetPyTeam();
|
||||
Py_INCREF(team);
|
||||
return team;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
// NOTE: this returns their PUBLIC account-id; we want to keep
|
||||
// actual account-ids as hidden as possible for now.
|
||||
auto PythonClassSessionPlayer::GetAccountID(PythonClassSessionPlayer* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(InGameThread());
|
||||
Player* p = self->player_->get();
|
||||
if (!p) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
std::string account_id = p->GetPublicAccountID();
|
||||
if (account_id.empty()) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
return PyUnicode_FromString(account_id.c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassSessionPlayer::SetData(PythonClassSessionPlayer* self,
|
||||
PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(InGameThread());
|
||||
PyObject* team_obj;
|
||||
PyObject* character_obj;
|
||||
PyObject* color_obj;
|
||||
PyObject* highlight_obj;
|
||||
static const char* kwlist[] = {"team", "character", "color", "highlight",
|
||||
nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(
|
||||
args, keywds, "OOOO", const_cast<char**>(kwlist), &team_obj,
|
||||
&character_obj, &color_obj, &highlight_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
Player* p = self->player_->get();
|
||||
if (!p) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
p->set_has_py_data(true);
|
||||
p->SetPyTeam(team_obj);
|
||||
p->SetPyCharacter(character_obj);
|
||||
p->SetPyColor(color_obj);
|
||||
p->SetPyHighlight(highlight_obj);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassSessionPlayer::GetIconInfo(PythonClassSessionPlayer* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(InGameThread());
|
||||
Player* p = self->player_->get();
|
||||
if (!p) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
std::vector<float> color = p->icon_tint_color();
|
||||
std::vector<float> color2 = p->icon_tint2_color();
|
||||
return Py_BuildValue(
|
||||
"{sssss(fff)s(fff)}", "texture", p->icon_tex_name().c_str(),
|
||||
"tint_texture", p->icon_tint_tex_name().c_str(), "tint_color", color[0],
|
||||
color[1], color[2], "tint2_color", color2[0], color2[1], color2[2]);
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassSessionPlayer::SetIconInfo(PythonClassSessionPlayer* self,
|
||||
PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(InGameThread());
|
||||
PyObject* texture_name_obj;
|
||||
PyObject* tint_texture_name_obj;
|
||||
PyObject* tint_color_obj;
|
||||
PyObject* tint2_color_obj;
|
||||
static const char* kwlist[] = {"texture", "tint_texture", "tint_color",
|
||||
"tint2_color", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(
|
||||
args, keywds, "OOOO", const_cast<char**>(kwlist), &texture_name_obj,
|
||||
&tint_texture_name_obj, &tint_color_obj, &tint2_color_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
Player* p = self->player_->get();
|
||||
if (!p) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
std::string texture_name = Python::GetPyString(texture_name_obj);
|
||||
std::string tint_texture_name = Python::GetPyString(tint_texture_name_obj);
|
||||
std::vector<float> tint_color = Python::GetPyFloats(tint_color_obj);
|
||||
if (tint_color.size() != 3) {
|
||||
throw Exception("Expected 3 floats for tint-color.", PyExcType::kValue);
|
||||
}
|
||||
std::vector<float> tint2_color = Python::GetPyFloats(tint2_color_obj);
|
||||
if (tint2_color.size() != 3) {
|
||||
throw Exception("Expected 3 floats for tint-color.", PyExcType::kValue);
|
||||
}
|
||||
p->SetIcon(texture_name, tint_texture_name, tint_color, tint2_color);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassSessionPlayer::SetActivity(PythonClassSessionPlayer* self,
|
||||
PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(InGameThread());
|
||||
PyObject* activity_obj;
|
||||
static const char* kwlist[] = {"activity", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O",
|
||||
const_cast<char**>(kwlist), &activity_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
Player* p = self->player_->get();
|
||||
if (!p) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
HostActivity* a;
|
||||
if (activity_obj == Py_None) {
|
||||
a = nullptr;
|
||||
} else {
|
||||
a = Python::GetPyHostActivity(activity_obj);
|
||||
}
|
||||
p->SetHostActivity(a);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassSessionPlayer::SetNode(PythonClassSessionPlayer* self,
|
||||
PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(InGameThread());
|
||||
PyObject* node_obj;
|
||||
static const char* kwlist[] = {"node", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O",
|
||||
const_cast<char**>(kwlist), &node_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
Player* p = self->player_->get();
|
||||
if (!p) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
Node* node;
|
||||
if (node_obj == Py_None) {
|
||||
node = nullptr;
|
||||
} else {
|
||||
node = Python::GetPyNode(node_obj);
|
||||
}
|
||||
p->set_node(node);
|
||||
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassSessionPlayer::GetIcon(PythonClassSessionPlayer* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(InGameThread());
|
||||
Player* p = self->player_->get();
|
||||
if (!p) {
|
||||
throw Exception(PyExcType::kSessionPlayerNotFound);
|
||||
}
|
||||
|
||||
// Now kindly ask the activity to load/return an icon for us.
|
||||
PythonRef args(Py_BuildValue("(O)", p->BorrowPyRef()), PythonRef::kSteal);
|
||||
PythonRef results;
|
||||
{
|
||||
Python::ScopedCallLabel label("get_player_icon");
|
||||
results = g_python->obj(Python::ObjID::kGetPlayerIconCall).Call(args);
|
||||
}
|
||||
return results.NewRef();
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassSessionPlayer::Dir(PythonClassSessionPlayer* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
// Start with the standard python dir listing.
|
||||
PyObject* dir_list = Python::generic_dir(reinterpret_cast<PyObject*>(self));
|
||||
assert(PyList_Check(dir_list));
|
||||
|
||||
// ..and add in our custom attr names.
|
||||
for (const char** name = extra_dir_attrs; *name != nullptr; name++) {
|
||||
PyList_Append(
|
||||
dir_list,
|
||||
PythonRef(PyUnicode_FromString(*name), PythonRef::kSteal).get());
|
||||
}
|
||||
PyList_Sort(dir_list);
|
||||
return dir_list;
|
||||
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
bool PythonClassSessionPlayer::s_create_empty_ = false;
|
||||
PyTypeObject PythonClassSessionPlayer::type_obj;
|
||||
PyMethodDef PythonClassSessionPlayer::tp_methods[] = {
|
||||
{"getname", (PyCFunction)GetName, METH_VARARGS | METH_KEYWORDS,
|
||||
"getname(full: bool = False, icon: bool = True) -> str\n"
|
||||
"\n"
|
||||
"Returns the player's name. If icon is True, the long version of the\n"
|
||||
"name may include an icon."},
|
||||
{"setname", (PyCFunction)SetName, METH_VARARGS | METH_KEYWORDS,
|
||||
"setname(name: str, full_name: str = None, real: bool = True)\n"
|
||||
" -> None\n"
|
||||
"\n"
|
||||
"Set the player's name to the provided string.\n"
|
||||
"A number will automatically be appended if the name is not unique from\n"
|
||||
"other players."},
|
||||
{"resetinput", (PyCFunction)ResetInput, METH_NOARGS,
|
||||
"resetinput() -> None\n"
|
||||
"\n"
|
||||
"Clears out the player's assigned input actions."},
|
||||
{"exists", (PyCFunction)Exists, METH_NOARGS,
|
||||
"exists() -> bool\n"
|
||||
"\n"
|
||||
"Return whether the underlying player is still in the game."},
|
||||
{"assigninput", (PyCFunction)AssignInputCall, METH_VARARGS | METH_KEYWORDS,
|
||||
"assigninput(type: Union[ba.InputType, Tuple[ba.InputType, ...]],\n"
|
||||
" call: Callable) -> None\n"
|
||||
"\n"
|
||||
"Set the python callable to be run for one or more types of input."},
|
||||
{"remove_from_game", (PyCFunction)RemoveFromGame, METH_NOARGS,
|
||||
"remove_from_game() -> None\n"
|
||||
"\n"
|
||||
"Removes the player from the game."},
|
||||
{"get_account_id", (PyCFunction)GetAccountID, METH_VARARGS | METH_KEYWORDS,
|
||||
"get_account_id() -> str\n"
|
||||
"\n"
|
||||
"Return the Account ID this player is signed in under, if\n"
|
||||
"there is one and it can be determined with relative certainty.\n"
|
||||
"Returns None otherwise. Note that this may require an active\n"
|
||||
"internet connection (especially for network-connected players)\n"
|
||||
"and may return None for a short while after a player initially\n"
|
||||
"joins (while verification occurs)."},
|
||||
{"setdata", (PyCFunction)SetData, METH_VARARGS | METH_KEYWORDS,
|
||||
"setdata(team: ba.SessionTeam, character: str,\n"
|
||||
" color: Sequence[float], highlight: Sequence[float]) -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
{"set_icon_info", (PyCFunction)SetIconInfo, METH_VARARGS | METH_KEYWORDS,
|
||||
"set_icon_info(texture: str, tint_texture: str,\n"
|
||||
" tint_color: Sequence[float], tint2_color: Sequence[float]) -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
{"setactivity", (PyCFunction)SetActivity, METH_VARARGS | METH_KEYWORDS,
|
||||
"setactivity(activity: Optional[ba.Activity]) -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
{"setnode", (PyCFunction)SetNode, METH_VARARGS | METH_KEYWORDS,
|
||||
"setnode(node: Optional[Node]) -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
{"get_icon", (PyCFunction)GetIcon, METH_NOARGS,
|
||||
"get_icon() -> Dict[str, Any]\n"
|
||||
"\n"
|
||||
"Returns the character's icon (images, colors, etc contained in a dict)"},
|
||||
{"get_icon_info", (PyCFunction)GetIconInfo, METH_NOARGS,
|
||||
"get_icon_info() -> Dict[str, Any]\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
{"__dir__", (PyCFunction)Dir, METH_NOARGS,
|
||||
"allows inclusion of our custom attrs in standard python dir()"},
|
||||
{nullptr}};
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
} // namespace ballistica
|
||||
62
src/ballistica/python/class/python_class_session_player.h
Normal file
62
src/ballistica/python/class/python_class_session_player.h
Normal file
@ -0,0 +1,62 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_SESSION_PLAYER_H_
|
||||
#define BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_SESSION_PLAYER_H_
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/python/class/python_class.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class PythonClassSessionPlayer : public PythonClass {
|
||||
public:
|
||||
static auto type_name() -> const char* { return "SessionPlayer"; }
|
||||
static void SetupType(PyTypeObject* obj);
|
||||
static auto Create(Player* player) -> PyObject*;
|
||||
static auto Check(PyObject* o) -> bool {
|
||||
return PyObject_TypeCheck(o, &type_obj);
|
||||
}
|
||||
static PyTypeObject type_obj;
|
||||
auto GetPlayer(bool doraise) const -> Player*;
|
||||
|
||||
private:
|
||||
static bool s_create_empty_;
|
||||
static PyMethodDef tp_methods[];
|
||||
static auto tp_repr(PythonClassSessionPlayer* self) -> PyObject*;
|
||||
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds)
|
||||
-> PyObject*;
|
||||
static void tp_dealloc(PythonClassSessionPlayer* self);
|
||||
static auto tp_getattro(PythonClassSessionPlayer* self, PyObject* attr)
|
||||
-> PyObject*;
|
||||
static auto tp_setattro(PythonClassSessionPlayer* self, PyObject* attr,
|
||||
PyObject* val) -> int;
|
||||
static auto GetName(PythonClassSessionPlayer* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject*;
|
||||
static auto Exists(PythonClassSessionPlayer* self) -> PyObject*;
|
||||
static auto SetName(PythonClassSessionPlayer* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject*;
|
||||
static auto ResetInput(PythonClassSessionPlayer* self) -> PyObject*;
|
||||
static auto AssignInputCall(PythonClassSessionPlayer* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject*;
|
||||
static auto RemoveFromGame(PythonClassSessionPlayer* self) -> PyObject*;
|
||||
static auto GetTeam(PythonClassSessionPlayer* self) -> PyObject*;
|
||||
static auto GetAccountID(PythonClassSessionPlayer* self) -> PyObject*;
|
||||
static auto SetData(PythonClassSessionPlayer* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject*;
|
||||
static auto GetIconInfo(PythonClassSessionPlayer* self) -> PyObject*;
|
||||
static auto SetIconInfo(PythonClassSessionPlayer* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject*;
|
||||
static auto SetActivity(PythonClassSessionPlayer* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject*;
|
||||
static auto SetNode(PythonClassSessionPlayer* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject*;
|
||||
static auto GetIcon(PythonClassSessionPlayer* self) -> PyObject*;
|
||||
static auto Dir(PythonClassSessionPlayer* self) -> PyObject*;
|
||||
Object::WeakRef<Player>* player_;
|
||||
static auto nb_bool(PythonClassSessionPlayer* self) -> int;
|
||||
static PyNumberMethods as_number_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_SESSION_PLAYER_H_
|
||||
108
src/ballistica/python/class/python_class_sound.cc
Normal file
108
src/ballistica/python/class/python_class_sound.cc
Normal file
@ -0,0 +1,108 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/class/python_class_sound.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/game/game.h"
|
||||
#include "ballistica/media/component/sound.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
auto PythonClassSound::tp_repr(PythonClassSound* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Object::Ref<Sound> m = *(self->sound_);
|
||||
return Py_BuildValue(
|
||||
"s", (std::string("<ba.Sound ")
|
||||
+ (m.exists() ? ("\"" + m->name() + "\"") : "(empty ref)") + ">")
|
||||
.c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
void PythonClassSound::SetupType(PyTypeObject* obj) {
|
||||
PythonClass::SetupType(obj);
|
||||
obj->tp_name = "ba.Sound";
|
||||
obj->tp_basicsize = sizeof(PythonClassSound);
|
||||
obj->tp_doc =
|
||||
"A reference to a sound.\n"
|
||||
"\n"
|
||||
"Category: Asset Classes\n"
|
||||
"\n"
|
||||
"Use ba.getsound() to instantiate one.";
|
||||
obj->tp_repr = (reprfunc)tp_repr;
|
||||
obj->tp_new = tp_new;
|
||||
obj->tp_dealloc = (destructor)tp_dealloc;
|
||||
}
|
||||
|
||||
auto PythonClassSound::Create(Sound* sound) -> PyObject* {
|
||||
s_create_empty_ = true; // prevent class from erroring on create
|
||||
auto* t = reinterpret_cast<PythonClassSound*>(
|
||||
PyObject_CallObject(reinterpret_cast<PyObject*>(&type_obj), nullptr));
|
||||
s_create_empty_ = false;
|
||||
if (!t) {
|
||||
throw Exception("ba.Sound creation failed.");
|
||||
}
|
||||
*(t->sound_) = sound;
|
||||
return reinterpret_cast<PyObject*>(t);
|
||||
}
|
||||
|
||||
auto PythonClassSound::GetSound(bool doraise) const -> Sound* {
|
||||
Sound* sound = sound_->get();
|
||||
if (!sound && doraise) {
|
||||
throw Exception("Invalid Sound.", PyExcType::kNotFound);
|
||||
}
|
||||
return sound;
|
||||
}
|
||||
|
||||
auto PythonClassSound::tp_new(PyTypeObject* type, PyObject* args,
|
||||
PyObject* kwds) -> PyObject* {
|
||||
auto* self = reinterpret_cast<PythonClassSound*>(type->tp_alloc(type, 0));
|
||||
if (self) {
|
||||
BA_PYTHON_TRY;
|
||||
if (!InGameThread()) {
|
||||
throw Exception(
|
||||
"ERROR: " + std::string(type_obj.tp_name)
|
||||
+ " objects must only be created in the game thread (current is ("
|
||||
+ GetCurrentThreadName() + ").");
|
||||
}
|
||||
if (!s_create_empty_) {
|
||||
throw Exception(
|
||||
"Can't instantiate Sounds directly; use ba.getsound() to get "
|
||||
"them.");
|
||||
}
|
||||
self->sound_ = new Object::Ref<Sound>();
|
||||
BA_PYTHON_NEW_CATCH;
|
||||
}
|
||||
return reinterpret_cast<PyObject*>(self);
|
||||
}
|
||||
|
||||
void PythonClassSound::Delete(Object::Ref<Sound>* ref) {
|
||||
assert(InGameThread());
|
||||
|
||||
// if we're the py-object for a sound, clear them out
|
||||
// (FIXME - wej should pass the old pointer in here to sanity-test that we
|
||||
// were their ref)
|
||||
if (ref->exists()) {
|
||||
(*ref)->ClearPyObject();
|
||||
}
|
||||
delete ref;
|
||||
}
|
||||
|
||||
void PythonClassSound::tp_dealloc(PythonClassSound* self) {
|
||||
BA_PYTHON_TRY;
|
||||
// these have to be deleted in the game thread - send the ptr along if need
|
||||
// be; otherwise do it immediately
|
||||
if (!InGameThread()) {
|
||||
Object::Ref<Sound>* s = self->sound_;
|
||||
g_game->PushCall([s] { Delete(s); });
|
||||
} else {
|
||||
Delete(self->sound_);
|
||||
}
|
||||
BA_PYTHON_DEALLOC_CATCH;
|
||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
||||
}
|
||||
|
||||
bool PythonClassSound::s_create_empty_ = false;
|
||||
PyTypeObject PythonClassSound::type_obj;
|
||||
|
||||
} // namespace ballistica
|
||||
34
src/ballistica/python/class/python_class_sound.h
Normal file
34
src/ballistica/python/class/python_class_sound.h
Normal file
@ -0,0 +1,34 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_SOUND_H_
|
||||
#define BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_SOUND_H_
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/python/class/python_class.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class PythonClassSound : public PythonClass {
|
||||
public:
|
||||
static auto type_name() -> const char* { return "Sound"; }
|
||||
static PyTypeObject type_obj;
|
||||
static auto tp_repr(PythonClassSound* self) -> PyObject*;
|
||||
static void SetupType(PyTypeObject* obj);
|
||||
static auto Create(Sound* sound) -> PyObject*;
|
||||
static auto Check(PyObject* o) -> bool {
|
||||
return PyObject_TypeCheck(o, &type_obj);
|
||||
}
|
||||
auto GetSound(bool doraise = true) const -> Sound*;
|
||||
|
||||
private:
|
||||
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* kwds)
|
||||
-> PyObject*;
|
||||
static void tp_dealloc(PythonClassSound* self);
|
||||
static void Delete(Object::Ref<Sound>* ref);
|
||||
static bool s_create_empty_;
|
||||
Object::Ref<Sound>* sound_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_SOUND_H_
|
||||
109
src/ballistica/python/class/python_class_texture.cc
Normal file
109
src/ballistica/python/class/python_class_texture.cc
Normal file
@ -0,0 +1,109 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/class/python_class_texture.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/game/game.h"
|
||||
#include "ballistica/media/component/texture.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
auto PythonClassTexture::tp_repr(PythonClassTexture* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Object::Ref<Texture> t = *(self->texture_);
|
||||
return Py_BuildValue(
|
||||
"s", (std::string("<ba.Texture ")
|
||||
+ (t.exists() ? ("\"" + t->name() + "\"") : "(empty ref)") + ">")
|
||||
.c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
void PythonClassTexture::SetupType(PyTypeObject* obj) {
|
||||
PythonClass::SetupType(obj);
|
||||
obj->tp_name = "ba.Texture";
|
||||
obj->tp_basicsize = sizeof(PythonClassTexture);
|
||||
obj->tp_doc =
|
||||
"A reference to a texture.\n"
|
||||
"\n"
|
||||
"Category: Asset Classes\n"
|
||||
"\n"
|
||||
"Use ba.gettexture() to instantiate one.";
|
||||
obj->tp_repr = (reprfunc)tp_repr;
|
||||
obj->tp_new = tp_new;
|
||||
obj->tp_dealloc = (destructor)tp_dealloc;
|
||||
}
|
||||
|
||||
auto PythonClassTexture::Create(Texture* texture) -> PyObject* {
|
||||
s_create_empty_ = true; // prevent class from erroring on create
|
||||
assert(texture != nullptr);
|
||||
auto* t = reinterpret_cast<PythonClassTexture*>(
|
||||
PyObject_CallObject(reinterpret_cast<PyObject*>(&type_obj), nullptr));
|
||||
s_create_empty_ = false;
|
||||
if (!t) {
|
||||
throw Exception("ba.Texture creation failed.");
|
||||
}
|
||||
*(t->texture_) = texture;
|
||||
return reinterpret_cast<PyObject*>(t);
|
||||
}
|
||||
|
||||
auto PythonClassTexture::GetTexture(bool doraise) const -> Texture* {
|
||||
Texture* texture = texture_->get();
|
||||
if (!texture && doraise) {
|
||||
throw Exception("Invalid Texture.", PyExcType::kNotFound);
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
|
||||
auto PythonClassTexture::tp_new(PyTypeObject* type, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
auto* self = reinterpret_cast<PythonClassTexture*>(type->tp_alloc(type, 0));
|
||||
if (self) {
|
||||
BA_PYTHON_TRY;
|
||||
if (!InGameThread()) {
|
||||
throw Exception(
|
||||
"ERROR: " + std::string(type_obj.tp_name)
|
||||
+ " objects must only be created in the game thread (current is ("
|
||||
+ GetCurrentThreadName() + ").");
|
||||
}
|
||||
if (!s_create_empty_) {
|
||||
throw Exception(
|
||||
"Can't instantiate Textures directly; use ba.gettexture() to get "
|
||||
"them.");
|
||||
}
|
||||
self->texture_ = new Object::Ref<Texture>();
|
||||
BA_PYTHON_NEW_CATCH;
|
||||
}
|
||||
return reinterpret_cast<PyObject*>(self);
|
||||
}
|
||||
|
||||
void PythonClassTexture::Delete(Object::Ref<Texture>* ref) {
|
||||
assert(InGameThread());
|
||||
|
||||
// If we're the py-object for a texture, kill our reference to it.
|
||||
// (FIXME - we should pass the old py obj pointer in here to
|
||||
// make sure that we were their python obj as a sanity test)
|
||||
if (ref->exists()) {
|
||||
(*ref)->ClearPyObject();
|
||||
}
|
||||
delete ref;
|
||||
}
|
||||
|
||||
void PythonClassTexture::tp_dealloc(PythonClassTexture* self) {
|
||||
BA_PYTHON_TRY;
|
||||
// These have to be deleted in the game thread - send the ptr along if need
|
||||
// be; otherwise do it immediately.
|
||||
if (!InGameThread()) {
|
||||
Object::Ref<Texture>* t = self->texture_;
|
||||
g_game->PushCall([t] { Delete(t); });
|
||||
} else {
|
||||
Delete(self->texture_);
|
||||
}
|
||||
BA_PYTHON_DEALLOC_CATCH;
|
||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
||||
}
|
||||
|
||||
bool PythonClassTexture::s_create_empty_ = false;
|
||||
PyTypeObject PythonClassTexture::type_obj;
|
||||
|
||||
} // namespace ballistica
|
||||
34
src/ballistica/python/class/python_class_texture.h
Normal file
34
src/ballistica/python/class/python_class_texture.h
Normal file
@ -0,0 +1,34 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_TEXTURE_H_
|
||||
#define BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_TEXTURE_H_
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/python/class/python_class.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class PythonClassTexture : public PythonClass {
|
||||
public:
|
||||
static auto type_name() -> const char* { return "Texture"; }
|
||||
static auto tp_repr(PythonClassTexture* self) -> PyObject*;
|
||||
static void SetupType(PyTypeObject* obj);
|
||||
static PyTypeObject type_obj;
|
||||
static auto Create(Texture* texture) -> PyObject*;
|
||||
static auto Check(PyObject* o) -> bool {
|
||||
return PyObject_TypeCheck(o, &type_obj);
|
||||
}
|
||||
auto GetTexture(bool doraise = true) const -> Texture*;
|
||||
|
||||
private:
|
||||
static bool s_create_empty_;
|
||||
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds)
|
||||
-> PyObject*;
|
||||
static void tp_dealloc(PythonClassTexture* self);
|
||||
static void Delete(Object::Ref<Texture>* ref);
|
||||
Object::Ref<Texture>* texture_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_TEXTURE_H_
|
||||
195
src/ballistica/python/class/python_class_timer.cc
Normal file
195
src/ballistica/python/class/python_class_timer.cc
Normal file
@ -0,0 +1,195 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/class/python_class_timer.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/game/game.h"
|
||||
#include "ballistica/game/host_activity.h"
|
||||
#include "ballistica/game/session/host_session.h"
|
||||
#include "ballistica/python/python_context_call_runnable.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
void PythonClassTimer::SetupType(PyTypeObject* obj) {
|
||||
PythonClass::SetupType(obj);
|
||||
obj->tp_name = "ba.Timer";
|
||||
obj->tp_basicsize = sizeof(PythonClassTimer);
|
||||
obj->tp_doc =
|
||||
"Timer(time: float, call: Callable[[], Any], repeat: bool = False,\n"
|
||||
" timetype: ba.TimeType = TimeType.SIM,\n"
|
||||
" timeformat: ba.TimeFormat = TimeFormat.SECONDS,\n"
|
||||
" suppress_format_warning: bool = False)\n"
|
||||
"\n"
|
||||
"Timers are used to run code at later points in time.\n"
|
||||
"\n"
|
||||
"Category: General Utility Classes\n"
|
||||
"\n"
|
||||
"This class encapsulates a timer in the current ba.Context.\n"
|
||||
"The underlying timer will be destroyed when either this object is\n"
|
||||
"no longer referenced or when its Context (Activity, etc.) dies. If you\n"
|
||||
"do not want to worry about keeping a reference to your timer around,\n"
|
||||
"you should use the ba.timer() function instead.\n"
|
||||
"\n"
|
||||
"time: length of time (in seconds by default) that the timer will wait\n"
|
||||
"before firing. Note that the actual delay experienced may vary\n "
|
||||
"depending on the timetype. (see below)\n"
|
||||
"\n"
|
||||
"call: A callable Python object. Note that the timer will retain a\n"
|
||||
"strong reference to the callable for as long as it exists, so you\n"
|
||||
"may want to look into concepts such as ba.WeakCall if that is not\n"
|
||||
"desired.\n"
|
||||
"\n"
|
||||
"repeat: if True, the timer will fire repeatedly, with each successive\n"
|
||||
"firing having the same delay as the first.\n"
|
||||
"\n"
|
||||
"timetype can be either 'sim', 'base', or 'real'. It defaults to\n"
|
||||
"'sim'. Types are explained below:\n"
|
||||
"\n"
|
||||
"'sim' time maps to local simulation time in ba.Activity or ba.Session\n"
|
||||
"Contexts. This means that it may progress slower in slow-motion play\n"
|
||||
"modes, stop when the game is paused, etc. This time type is not\n"
|
||||
"available in UI contexts.\n"
|
||||
"\n"
|
||||
"'base' time is also linked to gameplay in ba.Activity or ba.Session\n"
|
||||
"Contexts, but it progresses at a constant rate regardless of\n "
|
||||
"slow-motion states or pausing. It can, however, slow down or stop\n"
|
||||
"in certain cases such as network outages or game slowdowns due to\n"
|
||||
"cpu load. Like 'sim' time, this is unavailable in UI contexts.\n"
|
||||
"\n"
|
||||
"'real' time always maps to actual clock time with a bit of filtering\n"
|
||||
"added, regardless of Context. (the filtering prevents it from going\n"
|
||||
"backwards or jumping forward by large amounts due to the app being\n"
|
||||
"backgrounded, system time changing, etc.)\n"
|
||||
"Real time timers are currently only available in the UI context.\n"
|
||||
"\n"
|
||||
"the 'timeformat' arg defaults to SECONDS but can also be MILLISECONDS\n"
|
||||
"if you want to pass time as milliseconds.\n"
|
||||
"\n"
|
||||
"# Example: use a Timer object to print repeatedly for a few seconds:\n"
|
||||
"def say_it():\n"
|
||||
" ba.screenmessage('BADGER!')\n"
|
||||
"def stop_saying_it():\n"
|
||||
" self.t = None\n"
|
||||
" ba.screenmessage('MUSHROOM MUSHROOM!')\n"
|
||||
"# create our timer; it will run as long as we hold self.t\n"
|
||||
"self.t = ba.Timer(0.3, say_it, repeat=True)\n"
|
||||
"# now fire off a one-shot timer to kill it\n"
|
||||
"ba.timer(3.89, stop_saying_it)";
|
||||
obj->tp_new = tp_new;
|
||||
obj->tp_dealloc = (destructor)tp_dealloc;
|
||||
}
|
||||
|
||||
auto PythonClassTimer::tp_new(PyTypeObject* type, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
auto* self = reinterpret_cast<PythonClassTimer*>(type->tp_alloc(type, 0));
|
||||
if (self) {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
if (!InGameThread()) {
|
||||
throw Exception(
|
||||
"ERROR: " + std::string(type_obj.tp_name)
|
||||
+ " objects must only be created in the game thread (current is ("
|
||||
+ GetCurrentThreadName() + ").");
|
||||
}
|
||||
|
||||
self->context_ = new Context();
|
||||
|
||||
PyObject* length_obj{};
|
||||
int64_t length;
|
||||
int repeat{};
|
||||
int suppress_format_warning{};
|
||||
PyObject* call_obj{};
|
||||
PyObject* time_type_obj{};
|
||||
PyObject* time_format_obj{};
|
||||
static const char* kwlist[] = {"time", "call",
|
||||
"repeat", "timetype",
|
||||
"timeformat", "suppress_format_warning",
|
||||
nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(
|
||||
args, keywds, "OO|pOOp", const_cast<char**>(kwlist), &length_obj,
|
||||
&call_obj, &repeat, &time_type_obj, &time_format_obj,
|
||||
&suppress_format_warning)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto time_type = TimeType::kSim;
|
||||
if (time_type_obj != nullptr) {
|
||||
time_type = Python::GetPyEnum_TimeType(time_type_obj);
|
||||
}
|
||||
auto time_format = TimeFormat::kSeconds;
|
||||
if (time_format_obj != nullptr) {
|
||||
time_format = Python::GetPyEnum_TimeFormat(time_format_obj);
|
||||
}
|
||||
|
||||
#if BA_TEST_BUILD || BA_DEBUG_BUILD
|
||||
if (!suppress_format_warning) {
|
||||
g_python->TimeFormatCheck(time_format, length_obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
// We currently work with integer milliseconds internally.
|
||||
if (time_format == TimeFormat::kSeconds) {
|
||||
length = static_cast<int>(Python::GetPyDouble(length_obj) * 1000.0);
|
||||
} else if (time_format == TimeFormat::kMilliseconds) {
|
||||
length = Python::GetPyInt64(length_obj);
|
||||
} else {
|
||||
throw Exception("Invalid timeformat: '"
|
||||
+ std::to_string(static_cast<int>(time_format))
|
||||
+ "'.",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
if (length < 0) {
|
||||
throw Exception("Timer length < 0.", PyExcType::kValue);
|
||||
}
|
||||
|
||||
auto runnable(Object::New<Runnable, PythonContextCallRunnable>(call_obj));
|
||||
|
||||
self->time_type_ = time_type;
|
||||
|
||||
// Now just make sure we've got a valid context-target and ask us to
|
||||
// make us a timer.
|
||||
if (!self->context_->target.exists()) {
|
||||
throw Exception("Invalid current context.", PyExcType::kContext);
|
||||
}
|
||||
self->timer_id_ = self->context_->target->NewTimer(
|
||||
self->time_type_, length, static_cast<bool>(repeat), runnable);
|
||||
self->have_timer_ = true;
|
||||
|
||||
BA_PYTHON_NEW_CATCH;
|
||||
}
|
||||
return reinterpret_cast<PyObject*>(self);
|
||||
}
|
||||
void PythonClassTimer::DoDelete(bool have_timer, TimeType time_type,
|
||||
int timer_id, Context* context) {
|
||||
assert(InGameThread());
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
if (context->target.exists() && have_timer) {
|
||||
context->target->DeleteTimer(time_type, timer_id);
|
||||
}
|
||||
delete context;
|
||||
}
|
||||
|
||||
void PythonClassTimer::tp_dealloc(PythonClassTimer* self) {
|
||||
BA_PYTHON_TRY;
|
||||
// These have to be deleted in the game thread.
|
||||
if (!InGameThread()) {
|
||||
auto a0 = self->have_timer_;
|
||||
auto a1 = self->time_type_;
|
||||
auto a2 = self->timer_id_;
|
||||
auto a3 = self->context_;
|
||||
g_game->PushCall(
|
||||
[a0, a1, a2, a3] { PythonClassTimer::DoDelete(a0, a1, a2, a3); });
|
||||
} else {
|
||||
DoDelete(self->have_timer_, self->time_type_, self->timer_id_,
|
||||
self->context_);
|
||||
}
|
||||
BA_PYTHON_DEALLOC_CATCH;
|
||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
||||
}
|
||||
|
||||
PyTypeObject PythonClassTimer::type_obj;
|
||||
|
||||
} // namespace ballistica
|
||||
35
src/ballistica/python/class/python_class_timer.h
Normal file
35
src/ballistica/python/class/python_class_timer.h
Normal file
@ -0,0 +1,35 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_TIMER_H_
|
||||
#define BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_TIMER_H_
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
#include "ballistica/python/class/python_class.h"
|
||||
#include "ballistica/python/python.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class PythonClassTimer : public PythonClass {
|
||||
public:
|
||||
static auto type_name() -> const char* { return "Timer"; }
|
||||
static void SetupType(PyTypeObject* obj);
|
||||
static auto Check(PyObject* o) -> bool {
|
||||
return PyObject_TypeCheck(o, &type_obj);
|
||||
}
|
||||
static PyTypeObject type_obj;
|
||||
|
||||
private:
|
||||
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds)
|
||||
-> PyObject*;
|
||||
static void tp_dealloc(PythonClassTimer* self);
|
||||
static void DoDelete(bool have_timer, TimeType time_type, int timer_id,
|
||||
Context* context);
|
||||
TimeType time_type_;
|
||||
int timer_id_;
|
||||
Context* context_;
|
||||
bool have_timer_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_TIMER_H_
|
||||
350
src/ballistica/python/class/python_class_vec3.cc
Normal file
350
src/ballistica/python/class/python_class_vec3.cc
Normal file
@ -0,0 +1,350 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/class/python_class_vec3.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
#include "ballistica/python/python.h"
|
||||
|
||||
// FIXME:
|
||||
// We currently call abc.Sequence.register(_ba.Vec3) which registers us as
|
||||
// a Sequence type (so that isinstance(ba.Vec3(), abc.Sequence) == True).
|
||||
// However the abc module lists a few things as part of the Sequence interface
|
||||
// that we don't currently provide: index() and count()
|
||||
namespace ballistica {
|
||||
|
||||
// Ignore a few things that python macros do.
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "hicpp-signed-bitwise"
|
||||
#pragma ide diagnostic ignored "RedundantCast"
|
||||
|
||||
static const int kMemberCount = 3;
|
||||
|
||||
PyTypeObject PythonClassVec3::type_obj;
|
||||
PySequenceMethods PythonClassVec3::as_sequence_;
|
||||
PyNumberMethods PythonClassVec3::as_number_;
|
||||
|
||||
void PythonClassVec3::SetupType(PyTypeObject* obj) {
|
||||
PythonClass::SetupType(obj);
|
||||
obj->tp_name = "ba.Vec3";
|
||||
obj->tp_basicsize = sizeof(PythonClassVec3);
|
||||
obj->tp_doc =
|
||||
"A vector of 3 floats.\n"
|
||||
"\n"
|
||||
"Category: General Utility Classes\n"
|
||||
"\n"
|
||||
"These can be created the following ways (checked in this order):\n"
|
||||
"- with no args, all values are set to 0\n"
|
||||
"- with a single numeric arg, all values are set to that value\n"
|
||||
"- with a single three-member sequence arg, sequence values are copied\n"
|
||||
"- otherwise assumes individual x/y/z args (positional or keywords)"
|
||||
"\n"
|
||||
"Attributes:\n"
|
||||
"\n"
|
||||
" x: float\n"
|
||||
" The vector's X component.\n"
|
||||
"\n"
|
||||
" y: float\n"
|
||||
" The vector's Y component.\n"
|
||||
"\n"
|
||||
" z: float\n"
|
||||
" The vector's Z component.\n";
|
||||
|
||||
obj->tp_new = tp_new;
|
||||
obj->tp_repr = (reprfunc)tp_repr;
|
||||
obj->tp_methods = tp_methods;
|
||||
obj->tp_getattro = (getattrofunc)tp_getattro;
|
||||
obj->tp_setattro = (setattrofunc)tp_setattro;
|
||||
obj->tp_richcompare = (richcmpfunc)tp_richcompare;
|
||||
|
||||
// Sequence functionality.
|
||||
memset(&as_sequence_, 0, sizeof(as_sequence_));
|
||||
as_sequence_.sq_length = (lenfunc)sq_length;
|
||||
as_sequence_.sq_item = (ssizeargfunc)sq_item;
|
||||
as_sequence_.sq_ass_item = (ssizeobjargproc)sq_ass_item;
|
||||
obj->tp_as_sequence = &as_sequence_;
|
||||
|
||||
// Number functionality.
|
||||
memset(&as_number_, 0, sizeof(as_number_));
|
||||
as_number_.nb_add = (binaryfunc)nb_add;
|
||||
as_number_.nb_subtract = (binaryfunc)nb_subtract;
|
||||
as_number_.nb_multiply = (binaryfunc)nb_multiply;
|
||||
as_number_.nb_negative = (unaryfunc)nb_negative;
|
||||
obj->tp_as_number = &as_number_;
|
||||
|
||||
// Note: we could fill out the in-place versions of these
|
||||
// if we're not going for immutability..
|
||||
}
|
||||
|
||||
auto PythonClassVec3::Create(const Vector3f& val) -> PyObject* {
|
||||
auto obj =
|
||||
reinterpret_cast<PythonClassVec3*>(type_obj.tp_alloc(&type_obj, 0));
|
||||
if (obj) {
|
||||
obj->value = val;
|
||||
}
|
||||
return reinterpret_cast<PyObject*>(obj);
|
||||
}
|
||||
|
||||
auto PythonClassVec3::tp_new(PyTypeObject* type, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
auto self = reinterpret_cast<PythonClassVec3*>(type->tp_alloc(type, 0));
|
||||
if (self) {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
// Accept a numeric sequence of length 3.
|
||||
assert(args != nullptr);
|
||||
assert(PyTuple_Check(args));
|
||||
Py_ssize_t numargs = PyTuple_GET_SIZE(args);
|
||||
if (numargs == 1 && PySequence_Check(PyTuple_GET_ITEM(args, 0))) {
|
||||
auto vals = Python::GetPyFloats(PyTuple_GET_ITEM(args, 0));
|
||||
if (vals.size() != 3) {
|
||||
throw Exception("Expected a 3 member numeric sequence.",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
self->value.x = vals[0];
|
||||
self->value.y = vals[1];
|
||||
self->value.z = vals[2];
|
||||
} else if (numargs == 1
|
||||
&& Python::CanGetPyDouble(PyTuple_GET_ITEM(args, 0))) {
|
||||
float val = Python::GetPyFloat(PyTuple_GET_ITEM(args, 0));
|
||||
self->value.x = self->value.y = self->value.z = val;
|
||||
} else {
|
||||
// Otherwise interpret as individual x, y, z float vals defaulting to 0.
|
||||
static const char* kwlist[] = {"x", "y", "z", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(
|
||||
args, keywds, "|fff", const_cast<char**>(kwlist), &self->value.x,
|
||||
&self->value.y, &self->value.z)) {
|
||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
BA_PYTHON_NEW_CATCH;
|
||||
}
|
||||
return reinterpret_cast<PyObject*>(self);
|
||||
}
|
||||
|
||||
auto PythonClassVec3::tp_repr(PythonClassVec3* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
char buffer[128];
|
||||
snprintf(buffer, sizeof(buffer), "ba.Vec3(%f, %f, %f)", self->value.x,
|
||||
self->value.y, self->value.z);
|
||||
return Py_BuildValue("s", buffer);
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassVec3::sq_length(PythonClassVec3* self) -> Py_ssize_t {
|
||||
return kMemberCount;
|
||||
}
|
||||
|
||||
auto PythonClassVec3::sq_item(PythonClassVec3* self, Py_ssize_t i)
|
||||
-> PyObject* {
|
||||
if (i < 0 || i >= kMemberCount) {
|
||||
PyErr_SetString(PyExc_IndexError, "Vec3 index out of range");
|
||||
return nullptr;
|
||||
}
|
||||
return PyFloat_FromDouble(self->value.v[i]);
|
||||
}
|
||||
|
||||
auto PythonClassVec3::sq_ass_item(PythonClassVec3* self, Py_ssize_t i,
|
||||
PyObject* valobj) -> int {
|
||||
BA_PYTHON_TRY;
|
||||
if (i < 0 || i >= kMemberCount) {
|
||||
throw Exception("Vec3 index out of range.", PyExcType::kValue);
|
||||
}
|
||||
float val = Python::GetPyFloat(valobj);
|
||||
self->value.v[i] = val;
|
||||
return 0;
|
||||
BA_PYTHON_INT_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassVec3::nb_add(PythonClassVec3* l, PythonClassVec3* r)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
// We can add if both sides are Vec3.
|
||||
if (Check(reinterpret_cast<PyObject*>(l))
|
||||
&& Check(reinterpret_cast<PyObject*>(r))) {
|
||||
return Create(l->value + r->value);
|
||||
}
|
||||
|
||||
// Otherwise we got nothin'.
|
||||
Py_INCREF(Py_NotImplemented);
|
||||
return Py_NotImplemented;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassVec3::nb_subtract(PythonClassVec3* l, PythonClassVec3* r)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
// We can subtract if both sides are Vec3.
|
||||
if (Check(reinterpret_cast<PyObject*>(l))
|
||||
&& Check(reinterpret_cast<PyObject*>(r))) {
|
||||
return Create(l->value - r->value);
|
||||
}
|
||||
|
||||
// Otherwise we got nothin'.
|
||||
Py_INCREF(Py_NotImplemented);
|
||||
return Py_NotImplemented;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassVec3::nb_negative(PythonClassVec3* self) -> PyObject* {
|
||||
return Create(-self->value);
|
||||
}
|
||||
|
||||
auto PythonClassVec3::nb_multiply(PyObject* l, PyObject* r) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
// If left side is vec3.
|
||||
if (Check(l)) {
|
||||
// Try right as single number.
|
||||
if (Python::CanGetPyDouble(r)) {
|
||||
assert(Check(l));
|
||||
return Create(reinterpret_cast<PythonClassVec3*>(l)->value
|
||||
* Python::GetPyFloat(r));
|
||||
}
|
||||
|
||||
// Try right as a vec3-able value.
|
||||
if (Python::CanGetPyVector3f(r)) {
|
||||
Vector3f& lvec(reinterpret_cast<PythonClassVec3*>(l)->value);
|
||||
Vector3f rvec(Python::GetPyVector3f(r));
|
||||
return Create(
|
||||
Vector3f(lvec.x * rvec.x, lvec.y * rvec.y, lvec.z * rvec.z));
|
||||
}
|
||||
} else {
|
||||
// Ok, right must be vec3 (by definition).
|
||||
assert(Check(r));
|
||||
|
||||
// Try left as single value.
|
||||
if (Python::CanGetPyDouble(l)) {
|
||||
assert(Check(r));
|
||||
return Create(Python::GetPyFloat(l)
|
||||
* reinterpret_cast<PythonClassVec3*>(r)->value);
|
||||
}
|
||||
|
||||
// Try left as a vec3-able value.
|
||||
if (Python::CanGetPyVector3f(l)) {
|
||||
Vector3f lvec(Python::GetPyVector3f(l));
|
||||
Vector3f& rvec(reinterpret_cast<PythonClassVec3*>(r)->value);
|
||||
return Create(
|
||||
Vector3f(lvec.x * rvec.x, lvec.y * rvec.y, lvec.z * rvec.z));
|
||||
}
|
||||
}
|
||||
|
||||
// Ok we got nothin'.
|
||||
Py_INCREF(Py_NotImplemented);
|
||||
return Py_NotImplemented;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassVec3::tp_richcompare(PythonClassVec3* c1, PyObject* c2, int op)
|
||||
-> PyObject* {
|
||||
// Always return false against other types.
|
||||
if (!Check(c2)) {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
bool eq = (c1->value == (reinterpret_cast<PythonClassVec3*>(c2))->value);
|
||||
if (op == Py_EQ) {
|
||||
if (eq) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
} else if (op == Py_NE) {
|
||||
if (!eq) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
} else {
|
||||
// Don't support other ops.
|
||||
Py_RETURN_NOTIMPLEMENTED;
|
||||
}
|
||||
}
|
||||
|
||||
auto PythonClassVec3::Length(PythonClassVec3* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
return PyFloat_FromDouble(self->value.Length());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassVec3::Normalized(PythonClassVec3* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
return Create(self->value.Normalized());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassVec3::Dot(PythonClassVec3* self, PyObject* other) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
return PyFloat_FromDouble(self->value.Dot(Python::GetPyVector3f(other)));
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassVec3::Cross(PythonClassVec3* self, PyObject* other)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
return Create(Vector3f::Cross(self->value, Python::GetPyVector3f(other)));
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
PyMethodDef PythonClassVec3::tp_methods[] = {
|
||||
{"length", (PyCFunction)Length, METH_NOARGS,
|
||||
"length() -> float\n"
|
||||
"\n"
|
||||
"Returns the length of the vector."},
|
||||
{"normalized", (PyCFunction)Normalized, METH_NOARGS,
|
||||
"normalized() -> Vec3\n"
|
||||
"\n"
|
||||
"Returns a normalized version of the vector."},
|
||||
{"dot", (PyCFunction)Dot, METH_O,
|
||||
"dot(other: Vec3) -> float\n"
|
||||
"\n"
|
||||
"Returns the dot product of this vector and another."},
|
||||
{"cross", (PyCFunction)Cross, METH_O,
|
||||
"cross(other: Vec3) -> Vec3\n"
|
||||
"\n"
|
||||
"Returns the cross product of this vector and another."},
|
||||
{nullptr}};
|
||||
|
||||
auto PythonClassVec3::tp_getattro(PythonClassVec3* self, PyObject* attr)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(PyUnicode_Check(attr));
|
||||
|
||||
const char* s = PyUnicode_AsUTF8(attr);
|
||||
if (!strcmp(s, "x")) {
|
||||
return PyFloat_FromDouble(self->value.x);
|
||||
} else if (!strcmp(s, "y")) {
|
||||
return PyFloat_FromDouble(self->value.y);
|
||||
} else if (!strcmp(s, "z")) {
|
||||
return PyFloat_FromDouble(self->value.z);
|
||||
}
|
||||
return PyObject_GenericGetAttr(reinterpret_cast<PyObject*>(self), attr);
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassVec3::tp_setattro(PythonClassVec3* self, PyObject* attrobj,
|
||||
PyObject* valobj) -> int {
|
||||
BA_PYTHON_TRY;
|
||||
assert(PyUnicode_Check(attrobj));
|
||||
const char* attr = PyUnicode_AsUTF8(attrobj);
|
||||
float val = Python::GetPyFloat(valobj);
|
||||
if (!strcmp(attr, "x")) {
|
||||
self->value.x = val;
|
||||
} else if (!strcmp(attr, "y")) {
|
||||
self->value.y = val;
|
||||
} else if (!strcmp(attr, "z")) {
|
||||
self->value.z = val;
|
||||
} else {
|
||||
throw Exception("Attr '" + std::string(attr) + "' is not settable.",
|
||||
PyExcType::kAttribute);
|
||||
}
|
||||
return 0;
|
||||
BA_PYTHON_INT_CATCH;
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
} // namespace ballistica
|
||||
49
src/ballistica/python/class/python_class_vec3.h
Normal file
49
src/ballistica/python/class/python_class_vec3.h
Normal file
@ -0,0 +1,49 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_VEC3_H_
|
||||
#define BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_VEC3_H_
|
||||
|
||||
#include "ballistica/math/vector3f.h"
|
||||
#include "ballistica/python/class/python_class.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class PythonClassVec3 : public PythonClass {
|
||||
public:
|
||||
static auto type_name() -> const char* { return "Vec3"; }
|
||||
static void SetupType(PyTypeObject* obj);
|
||||
static auto Create(const Vector3f& val) -> PyObject*;
|
||||
static auto Check(PyObject* o) -> bool {
|
||||
return PyObject_TypeCheck(o, &type_obj);
|
||||
}
|
||||
static auto Length(PythonClassVec3* self) -> PyObject*;
|
||||
static auto Normalized(PythonClassVec3* self) -> PyObject*;
|
||||
static auto Dot(PythonClassVec3* self, PyObject* other) -> PyObject*;
|
||||
static auto Cross(PythonClassVec3* self, PyObject* other) -> PyObject*;
|
||||
static PyTypeObject type_obj;
|
||||
Vector3f value;
|
||||
|
||||
private:
|
||||
static PyMethodDef tp_methods[];
|
||||
static PySequenceMethods as_sequence_;
|
||||
static PyNumberMethods as_number_;
|
||||
static auto tp_repr(PythonClassVec3* self) -> PyObject*;
|
||||
static auto sq_length(PythonClassVec3* self) -> Py_ssize_t;
|
||||
static auto sq_item(PythonClassVec3* self, Py_ssize_t i) -> PyObject*;
|
||||
static auto sq_ass_item(PythonClassVec3* self, Py_ssize_t i, PyObject* val)
|
||||
-> int;
|
||||
static auto nb_add(PythonClassVec3* l, PythonClassVec3* r) -> PyObject*;
|
||||
static auto nb_subtract(PythonClassVec3* l, PythonClassVec3* r) -> PyObject*;
|
||||
static auto nb_multiply(PyObject* l, PyObject* r) -> PyObject*;
|
||||
static auto nb_negative(PythonClassVec3* self) -> PyObject*;
|
||||
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds)
|
||||
-> PyObject*;
|
||||
static auto tp_getattro(PythonClassVec3* self, PyObject* attr) -> PyObject*;
|
||||
static auto tp_richcompare(PythonClassVec3* c1, PyObject* c2, int op)
|
||||
-> PyObject*;
|
||||
static auto tp_setattro(PythonClassVec3* self, PyObject* attr, PyObject* val)
|
||||
-> int;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
#endif // BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_VEC3_H_
|
||||
288
src/ballistica/python/class/python_class_widget.cc
Normal file
288
src/ballistica/python/class/python_class_widget.cc
Normal file
@ -0,0 +1,288 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/class/python_class_widget.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/game/game.h"
|
||||
#include "ballistica/generic/utils.h"
|
||||
#include "ballistica/graphics/graphics.h"
|
||||
#include "ballistica/ui/widget/container_widget.h"
|
||||
#include "ballistica/ui/widget/widget.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
auto PythonClassWidget::nb_bool(PythonClassWidget* self) -> int {
|
||||
return self->widget_->exists();
|
||||
}
|
||||
|
||||
PyNumberMethods PythonClassWidget::as_number_;
|
||||
|
||||
void PythonClassWidget::SetupType(PyTypeObject* obj) {
|
||||
PythonClass::SetupType(obj);
|
||||
obj->tp_name = "ba.Widget";
|
||||
obj->tp_basicsize = sizeof(PythonClassWidget);
|
||||
obj->tp_doc =
|
||||
"Internal type for low level UI elements; buttons, windows, etc.\n"
|
||||
"\n"
|
||||
"Category: User Interface Classes\n"
|
||||
"\n"
|
||||
"This class represents a weak reference to a widget object\n"
|
||||
"in the internal c++ layer. Currently, functions such as\n"
|
||||
"ba.buttonwidget() must be used to instantiate or edit these.";
|
||||
obj->tp_new = tp_new;
|
||||
obj->tp_dealloc = (destructor)tp_dealloc;
|
||||
obj->tp_repr = (reprfunc)tp_repr;
|
||||
obj->tp_methods = tp_methods;
|
||||
|
||||
// we provide number methods only for bool functionality
|
||||
memset(&as_number_, 0, sizeof(as_number_));
|
||||
as_number_.nb_bool = (inquiry)nb_bool;
|
||||
obj->tp_as_number = &as_number_;
|
||||
}
|
||||
|
||||
auto PythonClassWidget::Create(Widget* widget) -> PyObject* {
|
||||
// Make sure we only have one python ref per widget.
|
||||
if (widget) {
|
||||
assert(!widget->has_py_ref());
|
||||
}
|
||||
|
||||
auto* py_widget = reinterpret_cast<PythonClassWidget*>(
|
||||
PyObject_CallObject(reinterpret_cast<PyObject*>(&type_obj), nullptr));
|
||||
if (!py_widget) throw Exception("ba.Widget creation failed");
|
||||
|
||||
*(py_widget->widget_) = widget;
|
||||
return reinterpret_cast<PyObject*>(py_widget);
|
||||
}
|
||||
|
||||
auto PythonClassWidget::GetWidget() const -> Widget* {
|
||||
Widget* w = widget_->get();
|
||||
if (!w) throw Exception("Invalid widget");
|
||||
return w;
|
||||
}
|
||||
|
||||
auto PythonClassWidget::tp_repr(PythonClassWidget* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Widget* w = self->widget_->get();
|
||||
return Py_BuildValue("s", (std::string("<Ballistica '")
|
||||
+ (w ? w->GetWidgetTypeName() : "<invalid>")
|
||||
+ "' widget " + Utils::PtrToString(w) + ">")
|
||||
.c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassWidget::tp_new(PyTypeObject* type, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
auto* self = reinterpret_cast<PythonClassWidget*>(type->tp_alloc(type, 0));
|
||||
if (self) {
|
||||
BA_PYTHON_TRY;
|
||||
if (!InGameThread()) {
|
||||
throw Exception(
|
||||
"ERROR: " + std::string(type_obj.tp_name)
|
||||
+ " objects must only be created in the game thread (current is ("
|
||||
+ GetCurrentThreadName() + ").");
|
||||
}
|
||||
self->widget_ = new Object::WeakRef<Widget>();
|
||||
BA_PYTHON_NEW_CATCH;
|
||||
}
|
||||
return reinterpret_cast<PyObject*>(self);
|
||||
}
|
||||
|
||||
void PythonClassWidget::tp_dealloc(PythonClassWidget* self) {
|
||||
BA_PYTHON_TRY;
|
||||
// these have to be destructed in the game thread - send them along to it if
|
||||
// need be
|
||||
if (!InGameThread()) {
|
||||
Object::WeakRef<Widget>* w = self->widget_;
|
||||
g_game->PushCall([w] { delete w; });
|
||||
} else {
|
||||
delete self->widget_;
|
||||
}
|
||||
BA_PYTHON_DEALLOC_CATCH;
|
||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
||||
}
|
||||
|
||||
auto PythonClassWidget::Exists(PythonClassWidget* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Widget* w = self->widget_->get();
|
||||
if (w) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassWidget::GetWidgetType(PythonClassWidget* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Widget* w = self->widget_->get();
|
||||
if (!w) {
|
||||
throw Exception(PyExcType::kWidgetNotFound);
|
||||
}
|
||||
return PyUnicode_FromString(w->GetWidgetTypeName().c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassWidget::Activate(PythonClassWidget* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Widget* w = self->widget_->get();
|
||||
if (!w) {
|
||||
throw Exception(PyExcType::kWidgetNotFound);
|
||||
}
|
||||
w->Activate();
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassWidget::GetChildren(PythonClassWidget* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Widget* w = self->widget_->get();
|
||||
if (!w) {
|
||||
throw Exception(PyExcType::kWidgetNotFound);
|
||||
}
|
||||
PyObject* py_list = PyList_New(0);
|
||||
auto* cw = dynamic_cast<ContainerWidget*>(w);
|
||||
if (cw) {
|
||||
for (auto&& i : cw->widgets()) {
|
||||
assert(i.exists());
|
||||
PyList_Append(py_list, i->BorrowPyRef());
|
||||
}
|
||||
}
|
||||
return py_list;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassWidget::GetSelectedChild(PythonClassWidget* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Widget* w = self->widget_->get();
|
||||
if (!w) {
|
||||
throw Exception(PyExcType::kWidgetNotFound);
|
||||
}
|
||||
auto* cw = dynamic_cast<ContainerWidget*>(w);
|
||||
if (cw) {
|
||||
Widget* selected_widget = cw->selected_widget();
|
||||
if (selected_widget) return selected_widget->NewPyRef();
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassWidget::GetScreenSpaceCenter(PythonClassWidget* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Widget* w = self->widget_->get();
|
||||
if (!w) {
|
||||
throw Exception(PyExcType::kWidgetNotFound);
|
||||
}
|
||||
float x, y;
|
||||
w->GetCenter(&x, &y);
|
||||
|
||||
// this gives us coords in the widget's parent's space; translate from that
|
||||
// to screen space
|
||||
if (ContainerWidget* parent = w->parent_widget()) {
|
||||
parent->WidgetPointToScreen(&x, &y);
|
||||
}
|
||||
// ..but we actually want to return points relative to the center of the
|
||||
// screen (so they're useful as stack-offset values)
|
||||
float screen_width = g_graphics->screen_virtual_width();
|
||||
float screen_height = g_graphics->screen_virtual_height();
|
||||
x -= screen_width * 0.5f;
|
||||
y -= screen_height * 0.5f;
|
||||
return Py_BuildValue("(ff)", x, y);
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassWidget::Delete(PythonClassWidget* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
int ignore_missing = true;
|
||||
static const char* kwlist[] = {"ignore_missing", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(
|
||||
args, keywds, "|i", const_cast<char**>(kwlist), &ignore_missing)) {
|
||||
return nullptr;
|
||||
}
|
||||
Widget* w = self->widget_->get();
|
||||
if (!w) {
|
||||
if (!ignore_missing) {
|
||||
throw Exception(PyExcType::kWidgetNotFound);
|
||||
}
|
||||
} else {
|
||||
ContainerWidget* p = w->parent_widget();
|
||||
if (p) {
|
||||
p->DeleteWidget(w);
|
||||
} else {
|
||||
Log("Error: Can't delete widget: no parent.");
|
||||
}
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassWidget::AddDeleteCallback(PythonClassWidget* self,
|
||||
PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
PyObject* call_obj;
|
||||
static const char* kwlist[] = {"call", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O",
|
||||
const_cast<char**>(kwlist), &call_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
Widget* w = self->widget_->get();
|
||||
if (!w) {
|
||||
throw Exception(PyExcType::kWidgetNotFound);
|
||||
}
|
||||
w->AddOnDeleteCall(call_obj);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
PyTypeObject PythonClassWidget::type_obj;
|
||||
PyMethodDef PythonClassWidget::tp_methods[] = {
|
||||
{"exists", (PyCFunction)Exists, METH_NOARGS,
|
||||
"exists() -> bool\n"
|
||||
"\n"
|
||||
"Returns whether the Widget still exists.\n"
|
||||
"Most functionality will fail on a nonexistent widget.\n"
|
||||
"\n"
|
||||
"Note that you can also use the boolean operator for this same\n"
|
||||
"functionality, so a statement such as \"if mywidget\" will do\n"
|
||||
"the right thing both for Widget objects and values of None."},
|
||||
{"get_widget_type", (PyCFunction)GetWidgetType, METH_NOARGS,
|
||||
"get_widget_type() -> str\n"
|
||||
"\n"
|
||||
"Return the internal type of the Widget as a string. Note that this is\n"
|
||||
"different from the Python ba.Widget type, which is the same for all\n"
|
||||
"widgets."},
|
||||
{"activate", (PyCFunction)Activate, METH_NOARGS,
|
||||
"activate() -> None\n"
|
||||
"\n"
|
||||
"Activates a widget; the same as if it had been clicked."},
|
||||
{"get_children", (PyCFunction)GetChildren, METH_NOARGS,
|
||||
"get_children() -> List[ba.Widget]\n"
|
||||
"\n"
|
||||
"Returns any child Widgets of this Widget."},
|
||||
{"get_screen_space_center", (PyCFunction)GetScreenSpaceCenter, METH_NOARGS,
|
||||
"get_screen_space_center() -> Tuple[float, float]\n"
|
||||
"\n"
|
||||
"Returns the coords of the Widget center relative to the center of the\n"
|
||||
"screen. This can be useful for placing pop-up windows and other special\n"
|
||||
"cases."},
|
||||
{"get_selected_child", (PyCFunction)GetSelectedChild, METH_NOARGS,
|
||||
"get_selected_child() -> Optional[ba.Widget]\n"
|
||||
"\n"
|
||||
"Returns the selected child Widget or None if nothing is selected."},
|
||||
// NOLINTNEXTLINE (signed bitwise stuff)
|
||||
{"delete", (PyCFunction)Delete, METH_VARARGS | METH_KEYWORDS,
|
||||
"delete(ignore_missing: bool = True) -> None\n"
|
||||
"\n"
|
||||
"Delete the Widget. Ignores already-deleted Widgets if ignore_missing\n"
|
||||
" is True; otherwise an Exception is thrown."},
|
||||
{"add_delete_callback", (PyCFunction)AddDeleteCallback,
|
||||
METH_VARARGS | METH_KEYWORDS, // NOLINT (signed bitwise stuff)
|
||||
"add_delete_callback(call: Callable) -> None\n"
|
||||
"\n"
|
||||
"Add a call to be run immediately after this widget is destroyed."},
|
||||
{nullptr}};
|
||||
|
||||
} // namespace ballistica
|
||||
45
src/ballistica/python/class/python_class_widget.h
Normal file
45
src/ballistica/python/class/python_class_widget.h
Normal file
@ -0,0 +1,45 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_WIDGET_H_
|
||||
#define BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_WIDGET_H_
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/python/class/python_class.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class PythonClassWidget : public PythonClass {
|
||||
public:
|
||||
static void SetupType(PyTypeObject* obj);
|
||||
static auto type_name() -> const char* { return "Widget"; }
|
||||
static auto Create(Widget* widget) -> PyObject*;
|
||||
static auto Check(PyObject* o) -> bool {
|
||||
return PyObject_TypeCheck(o, &type_obj);
|
||||
}
|
||||
static PyTypeObject type_obj;
|
||||
auto GetWidget() const -> Widget*;
|
||||
|
||||
private:
|
||||
static PyMethodDef tp_methods[];
|
||||
static auto tp_repr(PythonClassWidget* self) -> PyObject*;
|
||||
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds)
|
||||
-> PyObject*;
|
||||
static void tp_dealloc(PythonClassWidget* self);
|
||||
static auto Exists(PythonClassWidget* self) -> PyObject*;
|
||||
static auto GetWidgetType(PythonClassWidget* self) -> PyObject*;
|
||||
static auto Activate(PythonClassWidget* self) -> PyObject*;
|
||||
static auto GetChildren(PythonClassWidget* self) -> PyObject*;
|
||||
static auto GetSelectedChild(PythonClassWidget* self) -> PyObject*;
|
||||
static auto GetScreenSpaceCenter(PythonClassWidget* self) -> PyObject*;
|
||||
static auto Delete(PythonClassWidget* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject*;
|
||||
static auto AddDeleteCallback(PythonClassWidget* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject*;
|
||||
Object::WeakRef<Widget>* widget_;
|
||||
static auto nb_bool(PythonClassWidget* self) -> int;
|
||||
static PyNumberMethods as_number_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_CLASS_PYTHON_CLASS_WIDGET_H_
|
||||
1196
src/ballistica/python/methods/python_methods_app.cc
Normal file
1196
src/ballistica/python/methods/python_methods_app.cc
Normal file
File diff suppressed because it is too large
Load Diff
17
src/ballistica/python/methods/python_methods_app.h
Normal file
17
src/ballistica/python/methods/python_methods_app.h
Normal file
@ -0,0 +1,17 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_APP_H_
|
||||
#define BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_APP_H_
|
||||
|
||||
#include "ballistica/python/python_sys.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
/// App related individual python methods for our module.
|
||||
class PythonMethodsApp {
|
||||
public:
|
||||
static PyMethodDef methods_def[];
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
#endif // BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_APP_H_
|
||||
765
src/ballistica/python/methods/python_methods_gameplay.cc
Normal file
765
src/ballistica/python/methods/python_methods_gameplay.cc
Normal file
@ -0,0 +1,765 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/methods/python_methods_gameplay.h"
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/app/app.h"
|
||||
#include "ballistica/dynamics/bg/bg_dynamics.h"
|
||||
#include "ballistica/dynamics/collision.h"
|
||||
#include "ballistica/dynamics/dynamics.h"
|
||||
#include "ballistica/dynamics/material/material_action.h"
|
||||
#include "ballistica/game/connection/connection_to_client.h"
|
||||
#include "ballistica/game/game_stream.h"
|
||||
#include "ballistica/game/host_activity.h"
|
||||
#include "ballistica/game/player_spec.h"
|
||||
#include "ballistica/generic/json.h"
|
||||
#include "ballistica/graphics/graphics.h"
|
||||
#include "ballistica/input/device/input_device.h"
|
||||
#include "ballistica/media/component/sound.h"
|
||||
#include "ballistica/platform/platform.h"
|
||||
#include "ballistica/python/python.h"
|
||||
#include "ballistica/python/python_context_call_runnable.h"
|
||||
#include "ballistica/scene/node/node.h"
|
||||
#include "ballistica/scene/node/node_type.h"
|
||||
#include "ballistica/scene/scene.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Ignore signed bitwise stuff; python macros do it quite a bit.
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "hicpp-signed-bitwise"
|
||||
|
||||
auto PyNewNode(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("new_node");
|
||||
Node* n = g_python->DoNewNode(args, keywds);
|
||||
if (!n) {
|
||||
return nullptr;
|
||||
}
|
||||
return n->NewPyRef();
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyPrintNodes(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("print_nodes");
|
||||
HostActivity* host_activity =
|
||||
g_game->GetForegroundContext().GetHostActivity();
|
||||
if (!host_activity) {
|
||||
throw Exception(PyExcType::kContext);
|
||||
}
|
||||
Scene* scene = host_activity->scene();
|
||||
std::string s;
|
||||
int count = 1;
|
||||
for (auto&& i : scene->nodes()) {
|
||||
char buffer[128];
|
||||
snprintf(buffer, sizeof(buffer), "#%d: type: %-14s desc: %s", count,
|
||||
i->type()->name().c_str(), i->label().c_str());
|
||||
s += buffer;
|
||||
Log(buffer);
|
||||
count++;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetNodes(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_nodes");
|
||||
HostActivity* host_activity = Context::current().GetHostActivity();
|
||||
if (!host_activity) {
|
||||
throw Exception(PyExcType::kContext);
|
||||
}
|
||||
Scene* scene = host_activity->scene();
|
||||
PyObject* py_list = PyList_New(0);
|
||||
for (auto&& i : scene->nodes()) {
|
||||
PyList_Append(py_list, i->BorrowPyRef());
|
||||
}
|
||||
return py_list;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
static auto DoGetCollideValue(Dynamics* dynamics, const Collision* c,
|
||||
const char* name) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
if (!strcmp(name, "depth")) {
|
||||
return Py_BuildValue("f", c->depth);
|
||||
} else if (!strcmp(name, "position")) {
|
||||
return Py_BuildValue("(fff)", c->x, c->y, c->z);
|
||||
} else if (!strcmp(name, "sourcenode")) {
|
||||
if (!dynamics->in_collide_message()) {
|
||||
PyErr_SetString(
|
||||
PyExc_AttributeError,
|
||||
"collide value 'sourcenode' is only valid while processing "
|
||||
"collide messages");
|
||||
return nullptr;
|
||||
}
|
||||
Node* n = dynamics->GetActiveCollideSrcNode();
|
||||
if (n) {
|
||||
return n->NewPyRef();
|
||||
} else {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
} else if (!strcmp(name, "opposingnode")) {
|
||||
if (!dynamics->in_collide_message()) {
|
||||
PyErr_SetString(
|
||||
PyExc_AttributeError,
|
||||
"collide value 'opposingnode' is only valid while processing "
|
||||
"collide messages");
|
||||
return nullptr;
|
||||
}
|
||||
Node* n = dynamics->GetActiveCollideDstNode();
|
||||
if (n) {
|
||||
return n->NewPyRef();
|
||||
} else {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
} else if (!strcmp(name, "opposingbody")) {
|
||||
return Py_BuildValue("i", dynamics->GetCollideMessageReverseOrder()
|
||||
? c->body_id_2
|
||||
: c->body_id_1);
|
||||
} else {
|
||||
PyErr_SetString(
|
||||
PyExc_AttributeError,
|
||||
(std::string("\"") + name + "\" is not a valid collide value name")
|
||||
.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetCollisionInfo(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_collision_info");
|
||||
HostActivity* host_activity = Context::current().GetHostActivity();
|
||||
if (!host_activity) {
|
||||
throw Exception(PyExcType::kContext);
|
||||
}
|
||||
Dynamics* dynamics = host_activity->scene()->dynamics();
|
||||
assert(dynamics);
|
||||
PyObject* obj = nullptr;
|
||||
|
||||
// Take arg list as individual items or possibly a single tuple
|
||||
Py_ssize_t argc = PyTuple_GET_SIZE(args);
|
||||
if (argc > 1) {
|
||||
obj = args;
|
||||
} else if (argc == 1) {
|
||||
obj = PyTuple_GET_ITEM(args, 0);
|
||||
}
|
||||
Collision* c = dynamics->active_collision();
|
||||
if (!c) {
|
||||
PyErr_SetString(PyExc_RuntimeError,
|
||||
"This must be called from a collision callback.");
|
||||
return nullptr;
|
||||
}
|
||||
if (PyUnicode_Check(obj)) {
|
||||
return DoGetCollideValue(dynamics, c, PyUnicode_AsUTF8(obj));
|
||||
} else if (PyTuple_Check(obj)) {
|
||||
Py_ssize_t size = PyTuple_GET_SIZE(obj);
|
||||
PyObject* return_tuple = PyTuple_New(size);
|
||||
for (Py_ssize_t i = 0; i < size; i++) {
|
||||
PyObject* o = PyTuple_GET_ITEM(obj, i);
|
||||
if (PyUnicode_Check(o)) {
|
||||
PyObject* val_obj = DoGetCollideValue(dynamics, c, PyUnicode_AsUTF8(o));
|
||||
if (val_obj) {
|
||||
PyTuple_SetItem(return_tuple, i, val_obj);
|
||||
} else {
|
||||
Py_DECREF(return_tuple);
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
Py_DECREF(return_tuple);
|
||||
PyErr_SetString(PyExc_TypeError, "Expected a string as tuple member.");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return return_tuple;
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "Expected a string or tuple.");
|
||||
return nullptr;
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyCameraShake(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("camera_shake");
|
||||
assert(InGameThread());
|
||||
float intensity = 1.0f;
|
||||
static const char* kwlist[] = {"intensity", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "|f",
|
||||
const_cast<char**>(kwlist), &intensity)) {
|
||||
return nullptr;
|
||||
}
|
||||
g_graphics->LocalCameraShake(intensity);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyPlaySound(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("play_sound");
|
||||
|
||||
assert(InGameThread());
|
||||
PyObject* sound_obj;
|
||||
float volume = 1.0f;
|
||||
int host_only = 0;
|
||||
PyObject* pos_obj = Py_None;
|
||||
static const char* kwlist[] = {"sound", "volume", "position", "host_only",
|
||||
nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|fOp",
|
||||
const_cast<char**>(kwlist), &sound_obj,
|
||||
&volume, &pos_obj, &host_only)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Sound* sound = Python::GetPySound(sound_obj);
|
||||
|
||||
// Can play sounds in a host scene context.
|
||||
if (Scene* scene = Context::current().GetMutableScene()) {
|
||||
if (sound->scene() != scene) {
|
||||
throw Exception("Sound was not loaded in this context.",
|
||||
PyExcType::kContext);
|
||||
}
|
||||
if (pos_obj != Py_None) {
|
||||
std::vector<float> vals = Python::GetPyFloats(pos_obj);
|
||||
if (vals.size() != 3) {
|
||||
throw Exception("Expected 3 floats for pos (got "
|
||||
+ std::to_string(vals.size()) + ")",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
scene->PlaySoundAtPosition(sound, volume, vals[0], vals[1], vals[2],
|
||||
static_cast<bool>(host_only));
|
||||
} else {
|
||||
scene->PlaySound(sound, volume, static_cast<bool>(host_only));
|
||||
}
|
||||
} else {
|
||||
throw Exception("Can't play sounds in this context.", PyExcType::kContext);
|
||||
}
|
||||
|
||||
Py_RETURN_NONE;
|
||||
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyEmitFx(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("emit_fx");
|
||||
static const char* kwlist[] = {"position", "velocity", "count",
|
||||
"scale", "spread", "chunk_type",
|
||||
"emit_type", "tendril_type", nullptr};
|
||||
PyObject* pos_obj = Py_None;
|
||||
PyObject* vel_obj = Py_None;
|
||||
int count = 10;
|
||||
float scale = 1.0f;
|
||||
float spread = 1.0f;
|
||||
const char* chunk_type_str = "rock";
|
||||
const char* emit_type_str = "chunks";
|
||||
const char* tendril_type_str = "smoke";
|
||||
assert(InGameThread());
|
||||
if (!PyArg_ParseTupleAndKeywords(
|
||||
args, keywds, "O|Oiffsss", const_cast<char**>(kwlist), &pos_obj,
|
||||
&vel_obj, &count, &scale, &spread, &chunk_type_str, &emit_type_str,
|
||||
&tendril_type_str)) {
|
||||
return nullptr;
|
||||
}
|
||||
float x, y, z;
|
||||
assert(pos_obj);
|
||||
{
|
||||
std::vector<float> vals = Python::GetPyFloats(pos_obj);
|
||||
if (vals.size() != 3) {
|
||||
throw Exception("Expected 3 floats for position.", PyExcType::kValue);
|
||||
}
|
||||
x = vals[0];
|
||||
y = vals[1];
|
||||
z = vals[2];
|
||||
}
|
||||
float vx = 0.0f;
|
||||
float vy = 0.0f;
|
||||
float vz = 0.0f;
|
||||
if (vel_obj != Py_None) {
|
||||
std::vector<float> vals = Python::GetPyFloats(vel_obj);
|
||||
if (vals.size() != 3) {
|
||||
throw Exception("Expected 3 floats for velocity.", PyExcType::kValue);
|
||||
}
|
||||
vx = vals[0];
|
||||
vy = vals[1];
|
||||
vz = vals[2];
|
||||
}
|
||||
BGDynamicsChunkType chunk_type;
|
||||
if (!strcmp(chunk_type_str, "rock")) {
|
||||
chunk_type = BGDynamicsChunkType::kRock;
|
||||
} else if (!strcmp(chunk_type_str, "ice")) {
|
||||
chunk_type = BGDynamicsChunkType::kIce;
|
||||
} else if (!strcmp(chunk_type_str, "slime")) {
|
||||
chunk_type = BGDynamicsChunkType::kSlime;
|
||||
} else if (!strcmp(chunk_type_str, "metal")) {
|
||||
chunk_type = BGDynamicsChunkType::kMetal;
|
||||
} else if (!strcmp(chunk_type_str, "spark")) {
|
||||
chunk_type = BGDynamicsChunkType::kSpark;
|
||||
} else if (!strcmp(chunk_type_str, "splinter")) {
|
||||
chunk_type = BGDynamicsChunkType::kSplinter;
|
||||
} else if (!strcmp(chunk_type_str, "sweat")) {
|
||||
chunk_type = BGDynamicsChunkType::kSweat;
|
||||
} else {
|
||||
throw Exception(
|
||||
"Invalid chunk type: '" + std::string(chunk_type_str) + "'.",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
BGDynamicsTendrilType tendril_type;
|
||||
if (!strcmp(tendril_type_str, "smoke")) {
|
||||
tendril_type = BGDynamicsTendrilType::kSmoke;
|
||||
} else if (!strcmp(tendril_type_str, "thin_smoke")) {
|
||||
tendril_type = BGDynamicsTendrilType::kThinSmoke;
|
||||
} else if (!strcmp(tendril_type_str, "ice")) {
|
||||
tendril_type = BGDynamicsTendrilType::kIce;
|
||||
} else {
|
||||
throw Exception(
|
||||
"Invalid tendril type: '" + std::string(tendril_type_str) + "'.",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
BGDynamicsEmitType emit_type;
|
||||
if (!strcmp(emit_type_str, "chunks")) {
|
||||
emit_type = BGDynamicsEmitType::kChunks;
|
||||
} else if (!strcmp(emit_type_str, "stickers")) {
|
||||
emit_type = BGDynamicsEmitType::kStickers;
|
||||
} else if (!strcmp(emit_type_str, "tendrils")) {
|
||||
emit_type = BGDynamicsEmitType::kTendrils;
|
||||
} else if (!strcmp(emit_type_str, "distortion")) {
|
||||
emit_type = BGDynamicsEmitType::kDistortion;
|
||||
} else if (!strcmp(emit_type_str, "flag_stand")) {
|
||||
emit_type = BGDynamicsEmitType::kFlagStand;
|
||||
} else {
|
||||
throw Exception("Invalid emit type: '" + std::string(emit_type_str) + "'.",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
if (Scene* scene = Context::current().GetMutableScene()) {
|
||||
BGDynamicsEmission e;
|
||||
e.emit_type = emit_type;
|
||||
e.position = Vector3f(x, y, z);
|
||||
e.velocity = Vector3f(vx, vy, vz);
|
||||
e.count = count;
|
||||
e.scale = scale;
|
||||
e.spread = spread;
|
||||
e.chunk_type = chunk_type;
|
||||
e.tendril_type = tendril_type;
|
||||
if (GameStream* output_stream = scene->GetGameStream()) {
|
||||
output_stream->EmitBGDynamics(e);
|
||||
}
|
||||
#if !BA_HEADLESS_BUILD
|
||||
g_bg_dynamics->Emit(e);
|
||||
#endif // !BA_HEADLESS_BUILD
|
||||
} else {
|
||||
throw Exception("Can't emit bg dynamics in this context.",
|
||||
PyExcType::kContext);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PySetMapBounds(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("set_map_bounds");
|
||||
HostActivity* host_activity = Context::current().GetHostActivity();
|
||||
if (!host_activity) {
|
||||
throw Exception(PyExcType::kContext);
|
||||
}
|
||||
float xmin, ymin, zmin, xmax, ymax, zmax;
|
||||
assert(InGameThread());
|
||||
if (!PyArg_ParseTuple(args, "(ffffff)", &xmin, &ymin, &zmin, &xmax, &ymax,
|
||||
&zmax)) {
|
||||
return nullptr;
|
||||
}
|
||||
host_activity->scene()->SetMapBounds(xmin, ymin, zmin, xmax, ymax, zmax);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetForegroundHostActivity(PyObject* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_foreground_host_activity");
|
||||
static const char* kwlist[] = {nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
|
||||
const_cast<char**>(kwlist))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Note: we return None if not in the game thread.
|
||||
HostActivity* h = InGameThread()
|
||||
? g_game->GetForegroundContext().GetHostActivity()
|
||||
: nullptr;
|
||||
if (h != nullptr) {
|
||||
PyObject* obj = h->GetPyActivity();
|
||||
Py_INCREF(obj);
|
||||
return obj;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetGameRoster(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_game_roster");
|
||||
BA_PRECONDITION(InGameThread());
|
||||
static const char* kwlist[] = {nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
|
||||
const_cast<char**>(kwlist))) {
|
||||
return nullptr;
|
||||
}
|
||||
PythonRef py_client_list(PyList_New(0), PythonRef::kSteal);
|
||||
cJSON* party = g_game->game_roster();
|
||||
assert(party);
|
||||
int len = cJSON_GetArraySize(party);
|
||||
for (int i = 0; i < len; i++) {
|
||||
cJSON* client = cJSON_GetArrayItem(party, i);
|
||||
assert(client);
|
||||
cJSON* spec = cJSON_GetObjectItem(client, "spec");
|
||||
cJSON* players = cJSON_GetObjectItem(client, "p");
|
||||
PythonRef py_player_list(PyList_New(0), PythonRef::kSteal);
|
||||
if (players != nullptr) {
|
||||
int plen = cJSON_GetArraySize(players);
|
||||
for (int j = 0; j < plen; ++j) {
|
||||
cJSON* player = cJSON_GetArrayItem(players, j);
|
||||
if (player != nullptr) {
|
||||
cJSON* name = cJSON_GetObjectItem(player, "n");
|
||||
cJSON* py_name_full = cJSON_GetObjectItem(player, "nf");
|
||||
cJSON* id_obj = cJSON_GetObjectItem(player, "i");
|
||||
int id_val = id_obj ? id_obj->valueint : -1;
|
||||
if (name != nullptr && name->valuestring != nullptr
|
||||
&& py_name_full != nullptr && py_name_full->valuestring != nullptr
|
||||
&& id_val != -1) {
|
||||
PythonRef py_player(
|
||||
Py_BuildValue(
|
||||
"{sssssi}", "name",
|
||||
Utils::GetValidUTF8(name->valuestring, "ggr1").c_str(),
|
||||
"name_full",
|
||||
Utils::GetValidUTF8(py_name_full->valuestring, "ggr2")
|
||||
.c_str(),
|
||||
"id", id_val),
|
||||
PythonRef::kSteal);
|
||||
// This increments ref.
|
||||
PyList_Append(py_player_list.get(), py_player.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there's a client_id with this data, include it; otherwise pass None.
|
||||
cJSON* client_id = cJSON_GetObjectItem(client, "i");
|
||||
int clientid{};
|
||||
PythonRef client_id_ref;
|
||||
if (client_id != nullptr) {
|
||||
clientid = client_id->valueint;
|
||||
client_id_ref.Steal(PyLong_FromLong(clientid));
|
||||
} else {
|
||||
client_id_ref.Acquire(Py_None);
|
||||
}
|
||||
|
||||
// Let's also include a public account-id if we have one.
|
||||
std::string account_id;
|
||||
if (clientid == -1) {
|
||||
account_id = AppInternalGetPublicAccountID();
|
||||
} else {
|
||||
auto client2 = g_game->connections_to_clients().find(clientid);
|
||||
if (client2 != g_game->connections_to_clients().end()) {
|
||||
account_id = client2->second->peer_public_account_id();
|
||||
}
|
||||
}
|
||||
PythonRef account_id_ref;
|
||||
if (account_id.empty()) {
|
||||
account_id_ref.Acquire(Py_None);
|
||||
} else {
|
||||
account_id_ref.Steal(PyUnicode_FromString(account_id.c_str()));
|
||||
}
|
||||
|
||||
// Py_BuildValue steals a ref; gotta increment ourself (edit: NO IT DOESNT)
|
||||
// Py_INCREF(py_player_list.get());
|
||||
PythonRef py_client(
|
||||
Py_BuildValue(
|
||||
"{sssssOsOsO}", "display_string",
|
||||
(spec && spec->valuestring)
|
||||
? PlayerSpec(spec->valuestring).GetDisplayString().c_str()
|
||||
: "",
|
||||
"spec_string", (spec && spec->valuestring) ? spec->valuestring : "",
|
||||
"players", py_player_list.get(), "client_id", client_id_ref.get(),
|
||||
"account_id", account_id_ref.get()),
|
||||
PythonRef::kSteal);
|
||||
PyList_Append(py_client_list.get(),
|
||||
py_client.get()); // this increments ref
|
||||
}
|
||||
return py_client_list.NewRef();
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetScoresToBeat(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_scores_to_beat");
|
||||
const char* level;
|
||||
const char* config;
|
||||
PyObject* callback_obj = Py_None;
|
||||
static const char* kwlist[] = {"level", "config", "callback", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "ssO",
|
||||
const_cast<char**>(kwlist), &level, &config,
|
||||
&callback_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Allocate a Call object for this and pass its pointer to the main thread;
|
||||
// we'll ref/de-ref it when it comes back.
|
||||
auto* call = Object::NewDeferred<PythonContextCall>(callback_obj);
|
||||
g_app->PushGetScoresToBeatCall(level, config, call);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PySetDebugSpeedExponent(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("set_debug_speed_exponent");
|
||||
int speed;
|
||||
if (!PyArg_ParseTuple(args, "i", &speed)) {
|
||||
return nullptr;
|
||||
}
|
||||
HostActivity* host_activity = Context::current().GetHostActivity();
|
||||
if (!host_activity) {
|
||||
throw Exception(PyExcType::kContext);
|
||||
}
|
||||
#if BA_DEBUG_BUILD
|
||||
g_game->SetDebugSpeedExponent(speed);
|
||||
#else
|
||||
throw Exception("This call only functions in the debug build.");
|
||||
#endif
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetReplaySpeedExponent(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_replay_speed_exponent");
|
||||
assert(g_game);
|
||||
return PyLong_FromLong(g_game->replay_speed_exponent());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PySetReplaySpeedExponent(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("set_replay_speed_exponent");
|
||||
int speed;
|
||||
if (!PyArg_ParseTuple(args, "i", &speed)) return nullptr;
|
||||
assert(g_game);
|
||||
g_game->SetReplaySpeedExponent(speed);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyResetGameActivityTracking(PyObject* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("reset_game_activity_tracking");
|
||||
static const char* kwlist[] = {nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
|
||||
const_cast<char**>(kwlist))) {
|
||||
return nullptr;
|
||||
}
|
||||
if (g_game) {
|
||||
g_game->ResetActivityTracking();
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyResetRandomPlayerNames(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("reset_random_player_names");
|
||||
InputDevice::ResetRandomNames();
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetRandomNames(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_random_names");
|
||||
PyObject* list = PyList_New(0);
|
||||
const std::list<std::string>& random_name_list = Utils::GetRandomNameList();
|
||||
for (const auto& i : random_name_list) {
|
||||
assert(Utils::IsValidUTF8(i));
|
||||
PyObject* obj = PyUnicode_FromString(i.c_str());
|
||||
PyList_Append(list, obj);
|
||||
Py_DECREF(obj);
|
||||
}
|
||||
return list;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
PyMethodDef PythonMethodsGameplay::methods_def[] = {
|
||||
{"get_random_names", PyGetRandomNames, METH_VARARGS,
|
||||
"get_random_names() -> list\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Returns the random names used by the game."},
|
||||
|
||||
{"reset_random_player_names", (PyCFunction)PyResetRandomPlayerNames,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"reset_random_player_names() -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"reset_game_activity_tracking", (PyCFunction)PyResetGameActivityTracking,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"reset_game_activity_tracking() -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"set_replay_speed_exponent", PySetReplaySpeedExponent, METH_VARARGS,
|
||||
"set_replay_speed_exponent(speed: int) -> None\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Set replay speed. Actual displayed speed is pow(2,speed)."},
|
||||
|
||||
{"get_replay_speed_exponent", PyGetReplaySpeedExponent, METH_VARARGS,
|
||||
"get_replay_speed_exponent() -> int\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Returns current replay speed value. Actual displayed speed is "
|
||||
"pow(2,speed)."},
|
||||
|
||||
{"set_debug_speed_exponent", PySetDebugSpeedExponent, METH_VARARGS,
|
||||
"set_debug_speed_exponent(speed: int) -> None\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Sets the debug speed scale for the game. Actual speed is pow(2,speed)."},
|
||||
|
||||
{"get_scores_to_beat", (PyCFunction)PyGetScoresToBeat,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"get_scores_to_beat(level: str, config: str, callback: Callable) -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"get_game_roster", (PyCFunction)PyGetGameRoster,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"get_game_roster() -> List[Dict[str, Any]]\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"get_foreground_host_activity", (PyCFunction)PyGetForegroundHostActivity,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"get_foreground_host_activity() -> Optional[ba.Activity]\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Returns the ba.Activity currently in the foreground, or None if there\n"
|
||||
"is none.\n"},
|
||||
|
||||
{"set_map_bounds", (PyCFunction)PySetMapBounds,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"set_map_bounds(bounds: Tuple[float, float, float, float, float, float])\n"
|
||||
" -> None\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Set map bounds. Generally nodes that go outside of this box are "
|
||||
"killed."},
|
||||
|
||||
{"emitfx", (PyCFunction)PyEmitFx, METH_VARARGS | METH_KEYWORDS,
|
||||
"emitfx(position: Sequence[float],\n"
|
||||
" velocity: Optional[Sequence[float]] = None,\n"
|
||||
" count: int = 10, scale: float = 1.0, spread: float = 1.0,\n"
|
||||
" chunk_type: str = 'rock', emit_type: str ='chunks',\n"
|
||||
" tendril_type: str = 'smoke') -> None\n"
|
||||
"\n"
|
||||
"Emit particles, smoke, etc. into the fx sim layer.\n"
|
||||
"\n"
|
||||
"Category: Gameplay Functions\n"
|
||||
"\n"
|
||||
"The fx sim layer is a secondary dynamics simulation that runs in\n"
|
||||
"the background and just looks pretty; it does not affect gameplay.\n"
|
||||
"Note that the actual amount emitted may vary depending on graphics\n"
|
||||
"settings, exiting element counts, or other factors."},
|
||||
|
||||
{"playsound", (PyCFunction)PyPlaySound, METH_VARARGS | METH_KEYWORDS,
|
||||
"playsound(sound: Sound, volume: float = 1.0,\n"
|
||||
" position: Sequence[float] = None, host_only: bool = False) -> None\n"
|
||||
"\n"
|
||||
"Play a ba.Sound a single time.\n"
|
||||
"\n"
|
||||
"Category: Gameplay Functions\n"
|
||||
"\n"
|
||||
"If position is not provided, the sound will be at a constant volume\n"
|
||||
"everywhere. Position should be a float tuple of size 3."},
|
||||
|
||||
{"camerashake", (PyCFunction)PyCameraShake, METH_VARARGS | METH_KEYWORDS,
|
||||
"camerashake(intensity: float = 1.0) -> None\n"
|
||||
"\n"
|
||||
"Shake the camera.\n"
|
||||
"\n"
|
||||
"Category: Gameplay Functions\n"
|
||||
"\n"
|
||||
"Note that some cameras and/or platforms (such as VR) may not display\n"
|
||||
"camera-shake, so do not rely on this always being visible to the\n"
|
||||
"player as a gameplay cue."},
|
||||
|
||||
{"get_collision_info", PyGetCollisionInfo, METH_VARARGS,
|
||||
"get_collision_info(*args: Any) -> Any\n"
|
||||
"\n"
|
||||
"Return collision related values\n"
|
||||
"\n"
|
||||
"Category: Gameplay Functions\n"
|
||||
"\n"
|
||||
"Returns a single collision value or tuple of values such as location,\n"
|
||||
"depth, nodes involved, etc. Only call this in the handler of a\n"
|
||||
"collision-triggered callback or message"},
|
||||
|
||||
{"getnodes", PyGetNodes, METH_VARARGS,
|
||||
"getnodes() -> list\n"
|
||||
"\n"
|
||||
"Return all nodes in the current ba.Context."
|
||||
"\n"
|
||||
"Category: Gameplay Functions"},
|
||||
|
||||
{"printnodes", PyPrintNodes, METH_VARARGS,
|
||||
"printnodes() -> None\n"
|
||||
"\n"
|
||||
"Print various info about existing nodes; useful for debugging.\n"
|
||||
"\n"
|
||||
"Category: Gameplay Functions"},
|
||||
|
||||
{"newnode", (PyCFunction)PyNewNode, METH_VARARGS | METH_KEYWORDS,
|
||||
"newnode(type: str, owner: ba.Node = None,\n"
|
||||
"attrs: dict = None, name: str = None, delegate: Any = None)\n"
|
||||
" -> Node\n"
|
||||
"\n"
|
||||
"Add a node of the given type to the game.\n"
|
||||
"\n"
|
||||
"Category: Gameplay Functions\n"
|
||||
"\n"
|
||||
"If a dict is provided for 'attributes', the node's initial attributes\n"
|
||||
"will be set based on them.\n"
|
||||
"\n"
|
||||
"'name', if provided, will be stored with the node purely for debugging\n"
|
||||
"purposes. If no name is provided, an automatic one will be generated\n"
|
||||
"such as 'terrain@foo.py:30'.\n"
|
||||
"\n"
|
||||
"If 'delegate' is provided, Python messages sent to the node will go to\n"
|
||||
"that object's handlemessage() method. Note that the delegate is stored\n"
|
||||
"as a weak-ref, so the node itself will not keep the object alive.\n"
|
||||
"\n"
|
||||
"if 'owner' is provided, the node will be automatically killed when that\n"
|
||||
"object dies. 'owner' can be another node or a ba.Actor"},
|
||||
|
||||
{nullptr, nullptr, 0, nullptr}};
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
} // namespace ballistica
|
||||
17
src/ballistica/python/methods/python_methods_gameplay.h
Normal file
17
src/ballistica/python/methods/python_methods_gameplay.h
Normal file
@ -0,0 +1,17 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_GAMEPLAY_H_
|
||||
#define BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_GAMEPLAY_H_
|
||||
|
||||
#include "ballistica/python/python_sys.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
/// Gameplay related individual python methods for our module.
|
||||
class PythonMethodsGameplay {
|
||||
public:
|
||||
static PyMethodDef methods_def[];
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
#endif // BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_GAMEPLAY_H_
|
||||
315
src/ballistica/python/methods/python_methods_graphics.cc
Normal file
315
src/ballistica/python/methods/python_methods_graphics.cc
Normal file
@ -0,0 +1,315 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/methods/python_methods_graphics.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/game/game.h"
|
||||
#include "ballistica/graphics/graphics.h"
|
||||
#include "ballistica/graphics/text/text_graphics.h"
|
||||
#include "ballistica/platform/platform.h"
|
||||
#include "ballistica/python/python.h"
|
||||
#include "ballistica/python/python_context_call_runnable.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Ignore signed bitwise stuff; python macros do it quite a bit.
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "hicpp-signed-bitwise"
|
||||
|
||||
auto PyCharStr(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("charstr");
|
||||
PyObject* name_obj;
|
||||
static const char* kwlist[] = {"name", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O",
|
||||
const_cast<char**>(kwlist), &name_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
assert(g_game);
|
||||
auto id(Python::GetPyEnum_SpecialChar(name_obj));
|
||||
assert(Utils::IsValidUTF8(g_game->CharStr(id)));
|
||||
return PyUnicode_FromString(g_game->CharStr(id).c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PySafeColor(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("safecolor");
|
||||
PyObject* color_obj;
|
||||
float red, green, blue;
|
||||
float target_intensity = 0.6f;
|
||||
static const char* kwlist[] = {"color", "target_intensity", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|f",
|
||||
const_cast<char**>(kwlist), &color_obj,
|
||||
&target_intensity)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!PySequence_Check(color_obj)) {
|
||||
throw Exception("Expected a sequence.", PyExcType::kType);
|
||||
}
|
||||
int len = static_cast<int>(PySequence_Length(color_obj));
|
||||
if (len != 3 && len != 4) {
|
||||
throw Exception("Expected a 3 or 4 length sequence; got "
|
||||
+ Python::ObjToString(color_obj) + ".",
|
||||
PyExcType::kValue);
|
||||
}
|
||||
PythonRef red_obj(PySequence_GetItem(color_obj, 0), PythonRef::kSteal);
|
||||
PythonRef green_obj(PySequence_GetItem(color_obj, 1), PythonRef::kSteal);
|
||||
PythonRef blue_obj(PySequence_GetItem(color_obj, 2), PythonRef::kSteal);
|
||||
red = Python::GetPyFloat(red_obj.get());
|
||||
green = Python::GetPyFloat(green_obj.get());
|
||||
blue = Python::GetPyFloat(blue_obj.get());
|
||||
Graphics::GetSafeColor(&red, &green, &blue, target_intensity);
|
||||
if (len == 3) {
|
||||
return Py_BuildValue("(fff)", red, green, blue);
|
||||
} else {
|
||||
PythonRef alpha_obj(PySequence_GetItem(color_obj, 3), PythonRef::kSteal);
|
||||
float alpha = Python::GetPyFloat(alpha_obj.get());
|
||||
return Py_BuildValue("(ffff)", red, green, blue, alpha);
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetMaxGraphicsQuality(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_max_graphics_quality");
|
||||
if (g_graphics && g_graphics->has_supports_high_quality_graphics_value()
|
||||
&& g_graphics->supports_high_quality_graphics()) {
|
||||
return Py_BuildValue("s", "High");
|
||||
} else {
|
||||
return Py_BuildValue("s", "Medium");
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyEvaluateLstr(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("evaluate_lstr");
|
||||
const char* value;
|
||||
static const char* kwlist[] = {"value", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "s",
|
||||
const_cast<char**>(kwlist), &value)) {
|
||||
return nullptr;
|
||||
}
|
||||
return PyUnicode_FromString(
|
||||
g_game->CompileResourceString(value, "evaluate_lstr").c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetStringHeight(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_string_height");
|
||||
std::string s;
|
||||
int suppress_warning = 0;
|
||||
PyObject* s_obj;
|
||||
static const char* kwlist[] = {"string", "suppress_warning", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|i",
|
||||
const_cast<char**>(kwlist), &s_obj,
|
||||
&suppress_warning)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!suppress_warning) {
|
||||
BA_LOG_PYTHON_TRACE(
|
||||
"get_string_height() use is heavily discouraged as it reduces "
|
||||
"language-independence; pass suppress_warning=True if you must use "
|
||||
"it.");
|
||||
}
|
||||
s = Python::GetPyString(s_obj);
|
||||
#if BA_DEBUG_BUILD
|
||||
if (g_game->CompileResourceString(s, "get_string_height test") != s) {
|
||||
BA_LOG_PYTHON_TRACE(
|
||||
"resource-string passed to get_string_height; this should be avoided");
|
||||
}
|
||||
#endif
|
||||
assert(g_graphics);
|
||||
return Py_BuildValue("f", g_text_graphics->GetStringHeight(s));
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetStringWidth(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_string_width");
|
||||
std::string s;
|
||||
PyObject* s_obj;
|
||||
int suppress_warning = 0;
|
||||
static const char* kwlist[] = {"string", "suppress_warning", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|i",
|
||||
const_cast<char**>(kwlist), &s_obj,
|
||||
&suppress_warning)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!suppress_warning) {
|
||||
BA_LOG_PYTHON_TRACE(
|
||||
"get_string_width() use is heavily discouraged as it reduces "
|
||||
"language-independence; pass suppress_warning=True if you must use "
|
||||
"it.");
|
||||
}
|
||||
s = Python::GetPyString(s_obj);
|
||||
#if BA_DEBUG_BUILD
|
||||
if (g_game->CompileResourceString(s, "get_string_width debug test") != s) {
|
||||
BA_LOG_PYTHON_TRACE(
|
||||
"resource-string passed to get_string_width; this should be avoided");
|
||||
}
|
||||
#endif
|
||||
assert(g_graphics);
|
||||
return Py_BuildValue("f", g_text_graphics->GetStringWidth(s));
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyHaveChars(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("have_chars");
|
||||
std::string text;
|
||||
PyObject* text_obj;
|
||||
static const char* kwlist[] = {"text", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O",
|
||||
const_cast<char**>(kwlist), &text_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
text = Python::GetPyString(text_obj);
|
||||
if (TextGraphics::HaveChars(text)) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyAddCleanFrameCallback(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("add_clean_frame_callback");
|
||||
PyObject* call_obj;
|
||||
static const char* kwlist[] = {"call", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O",
|
||||
const_cast<char**>(kwlist), &call_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
g_python->AddCleanFrameCommand(Object::New<PythonContextCall>(call_obj));
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyHasGammaControl(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("has_gamma_control");
|
||||
// phasing this out; our old non-sdl2 mac has gamma controls but nothing newer
|
||||
// does...
|
||||
#if BA_OSTYPE_MACOS && !BA_SDL2_BUILD
|
||||
Py_RETURN_TRUE;
|
||||
#else
|
||||
Py_RETURN_FALSE;
|
||||
#endif
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetDisplayResolution(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_display_resolution");
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
bool have_res = g_platform->GetDisplayResolution(&x, &y);
|
||||
if (have_res) {
|
||||
return Py_BuildValue("(ii)", x, y);
|
||||
} else {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
PyMethodDef PythonMethodsGraphics::methods_def[] = {
|
||||
{"get_display_resolution", PyGetDisplayResolution, METH_VARARGS,
|
||||
"get_display_resolution() -> Optional[Tuple[int, int]]\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Return the currently selected display resolution for fullscreen\n"
|
||||
"display. Returns None if resolutions cannot be directly set."},
|
||||
|
||||
{"has_gamma_control", PyHasGammaControl, METH_VARARGS,
|
||||
"has_gamma_control() -> bool\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Returns whether the system can adjust overall screen gamma)"},
|
||||
|
||||
{"add_clean_frame_callback", (PyCFunction)PyAddCleanFrameCallback,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"add_clean_frame_callback(call: Callable) -> None\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Provide an object to be called once the next non-progress-bar-frame has\n"
|
||||
"been rendered. Useful for queueing things to load in the background\n"
|
||||
"without elongating any current progress-bar-load."},
|
||||
|
||||
{"have_chars", (PyCFunction)PyHaveChars, METH_VARARGS | METH_KEYWORDS,
|
||||
"have_chars(text: str) -> bool\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"get_string_width", (PyCFunction)PyGetStringWidth,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"get_string_width(string: str, suppress_warning: bool = False) -> float\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Given a string, returns its width using the standard small app\n"
|
||||
"font."},
|
||||
|
||||
{"get_string_height", (PyCFunction)PyGetStringHeight,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"get_string_height(string: str, suppress_warning: bool = False) -> float\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Given a string, returns its height using the standard small app\n"
|
||||
"font."},
|
||||
|
||||
{"evaluate_lstr", (PyCFunction)PyEvaluateLstr, METH_VARARGS | METH_KEYWORDS,
|
||||
"evaluate_lstr(value: str) -> str\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"get_max_graphics_quality", PyGetMaxGraphicsQuality, METH_VARARGS,
|
||||
"get_max_graphics_quality() -> str\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Return the max graphics-quality supported on the current hardware."},
|
||||
|
||||
{"safecolor", (PyCFunction)PySafeColor, METH_VARARGS | METH_KEYWORDS,
|
||||
"safecolor(color: Sequence[float], target_intensity: float = 0.6)\n"
|
||||
" -> Tuple[float, ...]\n"
|
||||
"\n"
|
||||
"Given a color tuple, return a color safe to display as text.\n"
|
||||
"\n"
|
||||
"Category: General Utility Functions\n"
|
||||
"\n"
|
||||
"Accepts tuples of length 3 or 4. This will slightly brighten very\n"
|
||||
"dark colors, etc."},
|
||||
|
||||
{"charstr", (PyCFunction)PyCharStr, METH_VARARGS | METH_KEYWORDS,
|
||||
"charstr(char_id: ba.SpecialChar) -> str\n"
|
||||
"\n"
|
||||
"Get a unicode string representing a special character.\n"
|
||||
"\n"
|
||||
"Category: General Utility Functions\n"
|
||||
"\n"
|
||||
"Note that these utilize the private-use block of unicode characters\n"
|
||||
"(U+E000-U+F8FF) and are specific to the game; exporting or rendering\n"
|
||||
"them elsewhere will be meaningless.\n"
|
||||
"\n"
|
||||
"see ba.SpecialChar for the list of available characters."},
|
||||
|
||||
{nullptr, nullptr, 0, nullptr}};
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
} // namespace ballistica
|
||||
17
src/ballistica/python/methods/python_methods_graphics.h
Normal file
17
src/ballistica/python/methods/python_methods_graphics.h
Normal file
@ -0,0 +1,17 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_GRAPHICS_H_
|
||||
#define BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_GRAPHICS_H_
|
||||
|
||||
#include "ballistica/python/python_sys.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
/// Graphics related individual python methods for our module.
|
||||
class PythonMethodsGraphics {
|
||||
public:
|
||||
static PyMethodDef methods_def[];
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
#endif // BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_GRAPHICS_H_
|
||||
394
src/ballistica/python/methods/python_methods_input.cc
Normal file
394
src/ballistica/python/methods/python_methods_input.cc
Normal file
@ -0,0 +1,394 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/methods/python_methods_input.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/app/app_globals.h"
|
||||
#include "ballistica/game/game.h"
|
||||
#include "ballistica/input/device/input_device.h"
|
||||
#include "ballistica/input/device/touch_input.h"
|
||||
#include "ballistica/input/input.h"
|
||||
#include "ballistica/platform/platform.h"
|
||||
#include "ballistica/python/python.h"
|
||||
#include "ballistica/ui/ui.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Ignore signed bitwise stuff; python macros do it quite a bit.
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "hicpp-signed-bitwise"
|
||||
|
||||
auto PyGetConfigurableGamePads(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_configurable_game_pads");
|
||||
std::vector<InputDevice*> gamepads = g_input->GetConfigurableGamePads();
|
||||
PyObject* list = PyList_New(0);
|
||||
for (auto&& i : gamepads) {
|
||||
PyObject* obj = i->NewPyRef();
|
||||
PyList_Append(list, obj);
|
||||
Py_DECREF(obj);
|
||||
}
|
||||
return list;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyHaveTouchScreenInput(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("have_touch_screen_input");
|
||||
if (g_app_globals->touch_input) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyStartListeningForWiiRemotes(PyObject* self, PyObject* args)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("start_listening_for_wii_remotes");
|
||||
g_platform->StartListeningForWiiRemotes();
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyStopListeningForWiiRemotes(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("stop_listening_for_wii_remotes");
|
||||
g_platform->StopListeningForWiiRemotes();
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PySetDeviceAccount(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("set_device_account");
|
||||
std::string name;
|
||||
PyObject* name_obj;
|
||||
static const char* kwlist[] = {"name", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O",
|
||||
const_cast<char**>(kwlist), &name_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
name = Python::GetPyString(name_obj);
|
||||
AccountType account_type;
|
||||
|
||||
// on headless builds we keep these distinct from regular
|
||||
// device accounts (so we get a 'ServerXXX' name, etc)
|
||||
#if BA_HEADLESS_BUILD
|
||||
account_type = AccountType::kServer;
|
||||
#else
|
||||
account_type = AccountType::kDevice;
|
||||
#endif
|
||||
g_game->PushSetAccountCall(account_type, AccountState::kSignedIn, name,
|
||||
g_platform->GetDeviceAccountID());
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetDeviceLoginID(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_device_login_id");
|
||||
assert(Utils::IsValidUTF8(g_platform->GetDeviceAccountID()));
|
||||
return PyUnicode_FromString(g_platform->GetDeviceAccountID().c_str());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PySetTouchscreenEditing(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("set_touchscreen_editing");
|
||||
int editing;
|
||||
if (!PyArg_ParseTuple(args, "p", &editing)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (g_app_globals->touch_input) {
|
||||
g_app_globals->touch_input->set_editing(static_cast<bool>(editing));
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyCaptureGamePadInput(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("capture_gamepad_input");
|
||||
assert(InGameThread());
|
||||
assert(g_python);
|
||||
PyObject* obj;
|
||||
if (!PyArg_ParseTuple(args, "O", &obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
g_python->CaptureGamePadInput(obj);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyReleaseGamePadInput(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("release_gamepad_input");
|
||||
assert(InGameThread());
|
||||
assert(g_python);
|
||||
g_python->ReleaseGamePadInput();
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyCaptureKeyboardInput(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("capture_keyboard_input");
|
||||
assert(InGameThread());
|
||||
if (!g_python) {
|
||||
return nullptr;
|
||||
}
|
||||
PyObject* obj;
|
||||
if (!PyArg_ParseTuple(args, "O", &obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
g_python->CaptureKeyboardInput(obj);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyReleaseKeyboardInput(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("release_keyboard_input");
|
||||
assert(InGameThread());
|
||||
if (!g_python) {
|
||||
return nullptr;
|
||||
}
|
||||
g_python->ReleaseKeyboardInput();
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyLockAllInput(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("lock_all_input");
|
||||
assert(InGameThread());
|
||||
assert(g_input);
|
||||
g_input->LockAllInput(false, Python::GetPythonFileLocation());
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyUnlockAllInput(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("unlock_all_input");
|
||||
assert(InGameThread());
|
||||
assert(g_input);
|
||||
g_input->UnlockAllInput(false, Python::GetPythonFileLocation());
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetUIInputDevice(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_ui_input_device");
|
||||
assert(InGameThread());
|
||||
static const char* kwlist[] = {nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
|
||||
const_cast<char**>(kwlist))) {
|
||||
return nullptr;
|
||||
}
|
||||
InputDevice* d = g_ui->GetUIInputDevice();
|
||||
if (d) {
|
||||
return d->NewPyRef();
|
||||
} else {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PySetUIInputDevice(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("set_ui_input_device");
|
||||
assert(InGameThread());
|
||||
static const char* kwlist[] = {"input", nullptr};
|
||||
PyObject* input_device_obj = Py_None;
|
||||
if (!PyArg_ParseTupleAndKeywords(
|
||||
args, keywds, "O", const_cast<char**>(kwlist), &input_device_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
g_ui->SetUIInputDevice((input_device_obj == Py_None)
|
||||
? nullptr
|
||||
: Python::GetPyInputDevice(input_device_obj));
|
||||
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetInputDevice(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_input_device");
|
||||
assert(InGameThread());
|
||||
const char* name;
|
||||
const char* unique_id;
|
||||
int doraise = true;
|
||||
static const char* kwlist[] = {"name", "unique_id", "doraise", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "ss|i",
|
||||
const_cast<char**>(kwlist), &name,
|
||||
&unique_id, &doraise)) {
|
||||
return nullptr;
|
||||
}
|
||||
InputDevice* d = g_input->GetInputDevice(name, unique_id);
|
||||
if (d) {
|
||||
return d->NewPyRef();
|
||||
} else {
|
||||
if (doraise) {
|
||||
throw Exception(std::string("Input device not found: '") + name + " "
|
||||
+ unique_id + "'.",
|
||||
PyExcType::kInputDeviceNotFound);
|
||||
} else {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetLocalActiveInputDevicesCount(PyObject* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_local_active_input_devices_count");
|
||||
static const char* kwlist[] = {nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
|
||||
const_cast<char**>(kwlist))) {
|
||||
return nullptr;
|
||||
}
|
||||
BA_PRECONDITION(g_input);
|
||||
return PyLong_FromLong(g_input->GetLocalActiveInputDeviceCount());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
PyMethodDef PythonMethodsInput::methods_def[] = {
|
||||
{"get_local_active_input_devices_count",
|
||||
(PyCFunction)PyGetLocalActiveInputDevicesCount,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"get_local_active_input_devices_count() -> int\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"getinputdevice", (PyCFunction)PyGetInputDevice,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"getinputdevice(name: str, unique_id: str, doraise: bool = True)\n"
|
||||
" -> <varies>\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Given a type name and a unique identifier, returns an InputDevice.\n"
|
||||
"Throws an Exception if the input-device is not found, or returns None\n"
|
||||
"if 'doraise' is False.\n"},
|
||||
|
||||
{"set_ui_input_device", (PyCFunction)PySetUIInputDevice,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"set_ui_input_device(input_device: Optional[ba.InputDevice]) -> None\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Sets the input-device that currently owns the user interface."},
|
||||
|
||||
{"get_ui_input_device", (PyCFunction)PyGetUIInputDevice,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"get_ui_input_device() -> ba.InputDevice\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Returns the input-device that currently owns the user interface, or\n"
|
||||
"None if there is none."},
|
||||
|
||||
{"unlock_all_input", PyUnlockAllInput, METH_VARARGS,
|
||||
"unlock_all_input() -> None\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Resumes normal keyboard, mouse, and gamepad event processing."},
|
||||
|
||||
{"lock_all_input", PyLockAllInput, METH_VARARGS,
|
||||
"lock_all_input() -> None\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Prevents all keyboard, mouse, and gamepad events from being processed."},
|
||||
|
||||
{"release_keyboard_input", PyReleaseKeyboardInput, METH_VARARGS,
|
||||
"release_keyboard_input() -> None\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Resumes normal keyboard event processing."},
|
||||
|
||||
{"capture_keyboard_input", PyCaptureKeyboardInput, METH_VARARGS,
|
||||
"capture_keyboard_input(call: Callable[[dict], None]) -> None\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Add a callable to be called for subsequent keyboard-game-pad events.\n"
|
||||
"The method is passed a dict containing info about the event."},
|
||||
|
||||
{"release_gamepad_input", PyReleaseGamePadInput, METH_VARARGS,
|
||||
"release_gamepad_input() -> None\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Resumes normal gamepad event processing."},
|
||||
|
||||
{"capture_gamepad_input", PyCaptureGamePadInput, METH_VARARGS,
|
||||
"capture_gamepad_input(call: Callable[[dict], None]) -> None\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Add a callable to be called for subsequent gamepad events.\n"
|
||||
"The method is passed a dict containing info about the event."},
|
||||
|
||||
{"set_touchscreen_editing", PySetTouchscreenEditing, METH_VARARGS,
|
||||
"set_touchscreen_editing(editing: bool) -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"get_device_login_id", (PyCFunction)PyGetDeviceLoginID,
|
||||
METH_VARARGS | METH_KEYWORDS, "internal"},
|
||||
|
||||
{"set_device_account", (PyCFunction)PySetDeviceAccount,
|
||||
METH_VARARGS | METH_KEYWORDS, "internal"},
|
||||
|
||||
{"stop_listening_for_wii_remotes", PyStopListeningForWiiRemotes,
|
||||
METH_VARARGS,
|
||||
"stop_listening_for_wii_remotes() -> None\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Stop listening for connections from wii remotes."},
|
||||
|
||||
{"start_listening_for_wii_remotes", PyStartListeningForWiiRemotes,
|
||||
METH_VARARGS,
|
||||
"start_listening_for_wii_remotes() -> None\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Start listening for connections from wii remotes."},
|
||||
|
||||
{"have_touchscreen_input", PyHaveTouchScreenInput, METH_VARARGS,
|
||||
"have_touchscreen_input() -> bool\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Returns whether or not a touch-screen input is present"},
|
||||
|
||||
{"get_configurable_game_pads", PyGetConfigurableGamePads, METH_VARARGS,
|
||||
"get_configurable_game_pads() -> list\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Returns a list of the currently connected gamepads that can be\n"
|
||||
"configured."},
|
||||
|
||||
{nullptr, nullptr, 0, nullptr}};
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
} // namespace ballistica
|
||||
18
src/ballistica/python/methods/python_methods_input.h
Normal file
18
src/ballistica/python/methods/python_methods_input.h
Normal file
@ -0,0 +1,18 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_INPUT_H_
|
||||
#define BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_INPUT_H_
|
||||
|
||||
#include "ballistica/python/python_sys.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Input related individual python methods for our module.
|
||||
class PythonMethodsInput {
|
||||
public:
|
||||
static PyMethodDef methods_def[];
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_INPUT_H_
|
||||
563
src/ballistica/python/methods/python_methods_media.cc
Normal file
563
src/ballistica/python/methods/python_methods_media.cc
Normal file
@ -0,0 +1,563 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/methods/python_methods_media.h"
|
||||
|
||||
#include <list>
|
||||
#if 0 // Cpplint errs w/o this, CLion errs with it. Hard to please everybody.
|
||||
#include <string>
|
||||
#endif
|
||||
|
||||
#include "ballistica/game/host_activity.h"
|
||||
#include "ballistica/game/session/host_session.h"
|
||||
#include "ballistica/graphics/graphics_server.h"
|
||||
#include "ballistica/media/component/collide_model.h"
|
||||
#include "ballistica/media/component/data.h"
|
||||
#include "ballistica/media/component/model.h"
|
||||
#include "ballistica/media/component/sound.h"
|
||||
#include "ballistica/media/component/texture.h"
|
||||
#include "ballistica/python/python.h"
|
||||
#include "ballistica/ui/ui.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Ignore signed bitwise stuff; python macros do it quite a bit.
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "hicpp-signed-bitwise"
|
||||
|
||||
auto PyGetTexture(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("gettexture");
|
||||
const char* name;
|
||||
static const char* kwlist[] = {"name", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "s",
|
||||
const_cast<char**>(kwlist), &name)) {
|
||||
return nullptr;
|
||||
}
|
||||
return Context::current_target().GetTexture(name)->NewPyRef();
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetPackageTexture(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("getpackagetexture");
|
||||
const char* name;
|
||||
PyObject* package_obj;
|
||||
static const char* kwlist[] = {"package", "name", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "Os",
|
||||
const_cast<char**>(kwlist), &package_obj,
|
||||
&name)) {
|
||||
return nullptr;
|
||||
}
|
||||
auto fullname = g_python->ValidatedPackageAssetName(package_obj, name);
|
||||
return Context::current_target().GetTexture(fullname)->NewPyRef();
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetSound(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("getsound");
|
||||
const char* name;
|
||||
static const char* kwlist[] = {"name", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "s",
|
||||
const_cast<char**>(kwlist), &name)) {
|
||||
return nullptr;
|
||||
}
|
||||
return Context::current_target().GetSound(name)->NewPyRef();
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetPackageSound(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("getpackagesound");
|
||||
const char* name;
|
||||
PyObject* package_obj;
|
||||
static const char* kwlist[] = {"package", "name", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "Os",
|
||||
const_cast<char**>(kwlist), &package_obj,
|
||||
&name)) {
|
||||
return nullptr;
|
||||
}
|
||||
auto fullname = g_python->ValidatedPackageAssetName(package_obj, name);
|
||||
return Context::current_target().GetSound(fullname)->NewPyRef();
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetData(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("getdata");
|
||||
const char* name;
|
||||
static const char* kwlist[] = {"name", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "s",
|
||||
const_cast<char**>(kwlist), &name)) {
|
||||
return nullptr;
|
||||
}
|
||||
return Context::current_target().GetData(name)->NewPyRef();
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetPackageData(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("getpackagedata");
|
||||
const char* name;
|
||||
PyObject* package_obj;
|
||||
static const char* kwlist[] = {"package", "name", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "Os",
|
||||
const_cast<char**>(kwlist), &package_obj,
|
||||
&name)) {
|
||||
return nullptr;
|
||||
}
|
||||
auto fullname = g_python->ValidatedPackageAssetName(package_obj, name);
|
||||
return Context::current_target().GetData(fullname)->NewPyRef();
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetModel(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("getmodel");
|
||||
const char* name;
|
||||
static const char* kwlist[] = {"name", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "s",
|
||||
const_cast<char**>(kwlist), &name)) {
|
||||
return nullptr;
|
||||
}
|
||||
return Context::current_target().GetModel(name)->NewPyRef();
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetPackageModel(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("getpackagemodel");
|
||||
const char* name;
|
||||
PyObject* package_obj;
|
||||
static const char* kwlist[] = {"package", "name", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "Os",
|
||||
const_cast<char**>(kwlist), &package_obj,
|
||||
&name)) {
|
||||
return nullptr;
|
||||
}
|
||||
auto fullname = g_python->ValidatedPackageAssetName(package_obj, name);
|
||||
return Context::current_target().GetTexture(fullname)->NewPyRef();
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetCollideModel(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("getcollidemodel");
|
||||
const char* name;
|
||||
static const char* kwlist[] = {"name", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "s",
|
||||
const_cast<char**>(kwlist), &name)) {
|
||||
return nullptr;
|
||||
}
|
||||
return Context::current_target().GetCollideModel(name)->NewPyRef();
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetPackageCollideModel(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("getpackagecollidemodel");
|
||||
const char* name;
|
||||
PyObject* package_obj;
|
||||
static const char* kwlist[] = {"package", "name", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "Os",
|
||||
const_cast<char**>(kwlist), &package_obj,
|
||||
&name)) {
|
||||
return nullptr;
|
||||
}
|
||||
auto fullname = g_python->ValidatedPackageAssetName(package_obj, name);
|
||||
return Context::current_target().GetCollideModel(fullname)->NewPyRef();
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyMusicPlayerStop(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("musicplayerstop");
|
||||
static const char* kwlist[] = {nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
|
||||
const_cast<char**>(kwlist))) {
|
||||
return nullptr;
|
||||
}
|
||||
g_platform->MusicPlayerStop();
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyMusicPlayerPlay(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("musicplayerplay");
|
||||
PyObject* files_obj;
|
||||
static const char* kwlist[] = {"files", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O",
|
||||
const_cast<char**>(kwlist), &files_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
g_platform->MusicPlayerPlay(files_obj);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyMusicPlayerSetVolume(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("musicplayersetvolume");
|
||||
float volume;
|
||||
static const char* kwlist[] = {"volume", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "f",
|
||||
const_cast<char**>(kwlist), &volume)) {
|
||||
return nullptr;
|
||||
}
|
||||
g_platform->MusicPlayerSetVolume(volume);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyMusicPlayerShutdown(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("musicplayershutdown");
|
||||
static const char* kwlist[] = {nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
|
||||
const_cast<char**>(kwlist))) {
|
||||
return nullptr;
|
||||
}
|
||||
g_platform->MusicPlayerShutdown();
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyReloadMedia(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("reloadmedia");
|
||||
assert(g_graphics_server);
|
||||
g_graphics_server->PushReloadMediaCall();
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetQRCodeTexture(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("getqrcodetexture");
|
||||
const char* url;
|
||||
static const char* kwlist[] = {"url", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "s",
|
||||
const_cast<char**>(kwlist), &url)) {
|
||||
return nullptr;
|
||||
}
|
||||
// FIXME - should add this to context; for now just hard-coded for UI though
|
||||
if (Context::current().GetUIContext() != nullptr) {
|
||||
// these textures aren't actually stored in the UI context;
|
||||
// we just make sure we're here so we're not corrupting a game/session.
|
||||
return Object::New<Texture>(url)->NewPyRef();
|
||||
} else {
|
||||
throw Exception("QR-Code textures can only be created in the UI context.",
|
||||
PyExcType::kContext);
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyMacMusicAppInit(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("macmusicappinit");
|
||||
g_platform->MacMusicAppInit();
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyMacMusicAppGetVolume(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("macmusicappgetvolume");
|
||||
return PyLong_FromLong(g_platform->MacMusicAppGetVolume());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyMacMusicAppSetVolume(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("macmusicappsetvolume");
|
||||
int volume;
|
||||
static const char* kwlist[] = {"volume", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "i",
|
||||
const_cast<char**>(kwlist), &volume)) {
|
||||
return nullptr;
|
||||
}
|
||||
g_platform->MacMusicAppSetVolume(volume);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyMacMusicAppGetLibrarySource(PyObject* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("macmusicappgetlibrarysource");
|
||||
g_platform->MacMusicAppGetLibrarySource();
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyMacMusicAppStop(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("macmusicappstop");
|
||||
g_platform->MacMusicAppStop();
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyMacMusicAppPlayPlaylist(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("macmusicappplayplaylist");
|
||||
std::string playlist;
|
||||
PyObject* playlist_obj;
|
||||
static const char* kwlist[] = {"playlist", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O",
|
||||
const_cast<char**>(kwlist), &playlist_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
playlist = Python::GetPyString(playlist_obj);
|
||||
if (g_platform->MacMusicAppPlayPlaylist(playlist)) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyMacMusicAppGetPlaylists(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("macmusicappgetplaylists");
|
||||
PyObject* py_list = PyList_New(0);
|
||||
std::list<std::string> playlists = g_platform->MacMusicAppGetPlaylists();
|
||||
for (auto&& i : playlists) {
|
||||
PyObject* str_obj = PyUnicode_FromString(i.c_str());
|
||||
PyList_Append(py_list, str_obj);
|
||||
Py_DECREF(str_obj);
|
||||
}
|
||||
return py_list;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyIsOSPlayingMusic(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("isosplayingmusic");
|
||||
if (g_platform->IsOSPlayingMusic()) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
PyMethodDef PythonMethodsMedia::methods_def[] = {
|
||||
{"is_os_playing_music", (PyCFunction)PyIsOSPlayingMusic,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"is_os_playing_music() -> bool\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Tells whether the OS is currently playing music of some sort.\n"
|
||||
"\n"
|
||||
"(Used to determine whether the game should avoid playing its own)"},
|
||||
|
||||
{"mac_music_app_init", (PyCFunction)PyMacMusicAppInit,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"mac_music_app_init() -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"mac_music_app_get_volume", (PyCFunction)PyMacMusicAppGetVolume,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"mac_music_app_get_volume() -> int\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"mac_music_app_set_volume", (PyCFunction)PyMacMusicAppSetVolume,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"mac_music_app_set_volume(volume: int) -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"mac_music_app_get_library_source",
|
||||
(PyCFunction)PyMacMusicAppGetLibrarySource, METH_VARARGS | METH_KEYWORDS,
|
||||
"mac_music_app_get_library_source() -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"mac_music_app_stop", (PyCFunction)PyMacMusicAppStop,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"mac_music_app_stop() -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"mac_music_app_play_playlist", (PyCFunction)PyMacMusicAppPlayPlaylist,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"mac_music_app_play_playlist(playlist: str) -> bool\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"mac_music_app_get_playlists", (PyCFunction)PyMacMusicAppGetPlaylists,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"mac_music_app_get_playlists() -> List[str]\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"get_qrcode_texture", (PyCFunction)PyGetQRCodeTexture,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"get_qrcode_texture(url: str) -> ba.Texture\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"reload_media", PyReloadMedia, METH_VARARGS,
|
||||
"reload_media() -> None\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Reload all currently loaded game media; useful for\n"
|
||||
"development/debugging."},
|
||||
|
||||
{"music_player_shutdown", (PyCFunction)PyMusicPlayerShutdown,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"music_player_shutdown() -> None\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Finalizes internal music file playback (for internal use)"},
|
||||
|
||||
{"music_player_set_volume", (PyCFunction)PyMusicPlayerSetVolume,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"music_player_set_volume(volume: float) -> None\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Sets internal music player volume (for internal use)"},
|
||||
|
||||
{"music_player_play", (PyCFunction)PyMusicPlayerPlay,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"music_player_play(files: Any) -> None\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Starts internal music file playback (for internal use)"},
|
||||
|
||||
{"music_player_stop", (PyCFunction)PyMusicPlayerStop,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"music_player_stop() -> None\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Stops internal music file playback (for internal use)"},
|
||||
|
||||
{"getcollidemodel", (PyCFunction)PyGetCollideModel,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"getcollidemodel(name: str) -> ba.CollideModel\n"
|
||||
"\n"
|
||||
"Return a collide-model, loading it if necessary.\n"
|
||||
"\n"
|
||||
"Category: Asset Functions\n"
|
||||
"\n"
|
||||
"Collide-models are used in physics calculations for such things as\n"
|
||||
"terrain.\n"
|
||||
"\n"
|
||||
"Note that this function returns immediately even if the media has yet\n"
|
||||
"to be loaded. To avoid hitches, instantiate your media objects in\n"
|
||||
"advance of when you will be using them, allowing time for them to load\n"
|
||||
"in the background if necessary."},
|
||||
|
||||
{"get_package_collide_model", (PyCFunction)PyGetPackageCollideModel,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"get_package_collide_model(package: ba.AssetPackage, name: str)\n"
|
||||
"-> ba.CollideModel\n"
|
||||
"\n"
|
||||
"(internal)\n"},
|
||||
|
||||
{"getmodel", (PyCFunction)PyGetModel, METH_VARARGS | METH_KEYWORDS,
|
||||
"getmodel(name: str) -> ba.Model\n"
|
||||
"\n"
|
||||
"Return a model, loading it if necessary.\n"
|
||||
"\n"
|
||||
"Category: Asset Functions\n"
|
||||
"\n"
|
||||
"Note that this function returns immediately even if the media has yet\n"
|
||||
"to be loaded. To avoid hitches, instantiate your media objects in\n"
|
||||
"advance of when you will be using them, allowing time for them to load\n"
|
||||
"in the background if necessary."},
|
||||
|
||||
{"get_package_model", (PyCFunction)PyGetPackageModel,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"get_package_model(package: ba.AssetPackage, name: str) -> ba.Model\n"
|
||||
"\n"
|
||||
"(internal)\n"},
|
||||
|
||||
{"getsound", (PyCFunction)PyGetSound, METH_VARARGS | METH_KEYWORDS,
|
||||
"getsound(name: str) -> ba.Sound\n"
|
||||
"\n"
|
||||
"Return a sound, loading it if necessary.\n"
|
||||
"\n"
|
||||
"Category: Asset Functions\n"
|
||||
"\n"
|
||||
"Note that this function returns immediately even if the media has yet\n"
|
||||
"to be loaded. To avoid hitches, instantiate your media objects in\n"
|
||||
"advance of when you will be using them, allowing time for them to load\n"
|
||||
"in the background if necessary."},
|
||||
|
||||
{"get_package_sound", (PyCFunction)PyGetPackageSound,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"get_package_sound(package: ba.AssetPackage, name: str) -> ba.Sound\n"
|
||||
"\n"
|
||||
"(internal).\n"},
|
||||
|
||||
{"getdata", (PyCFunction)PyGetData, METH_VARARGS | METH_KEYWORDS,
|
||||
"getdata(name: str) -> ba.Data\n"
|
||||
"\n"
|
||||
"Return a data, loading it if necessary.\n"
|
||||
"\n"
|
||||
"Category: Asset Functions\n"
|
||||
"\n"
|
||||
"Note that this function returns immediately even if the media has yet\n"
|
||||
"to be loaded. To avoid hitches, instantiate your media objects in\n"
|
||||
"advance of when you will be using them, allowing time for them to load\n"
|
||||
"in the background if necessary."},
|
||||
|
||||
{"get_package_data", (PyCFunction)PyGetPackageData,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"get_package_data(package: ba.AssetPackage, name: str) -> ba.Data\n"
|
||||
"\n"
|
||||
"(internal).\n"},
|
||||
|
||||
{"gettexture", (PyCFunction)PyGetTexture, METH_VARARGS | METH_KEYWORDS,
|
||||
"gettexture(name: str) -> ba.Texture\n"
|
||||
"\n"
|
||||
"Return a texture, loading it if necessary.\n"
|
||||
"\n"
|
||||
"Category: Asset Functions\n"
|
||||
"\n"
|
||||
"Note that this function returns immediately even if the media has yet\n"
|
||||
"to be loaded. To avoid hitches, instantiate your media objects in\n"
|
||||
"advance of when you will be using them, allowing time for them to load\n"
|
||||
"in the background if necessary."},
|
||||
|
||||
{"get_package_texture", (PyCFunction)PyGetPackageTexture,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"get_package_texture(package: ba.AssetPackage, name: str) -> ba.Texture\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{nullptr, nullptr, 0, nullptr}};
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
} // namespace ballistica
|
||||
18
src/ballistica/python/methods/python_methods_media.h
Normal file
18
src/ballistica/python/methods/python_methods_media.h
Normal file
@ -0,0 +1,18 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_MEDIA_H_
|
||||
#define BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_MEDIA_H_
|
||||
|
||||
#include "ballistica/python/python_sys.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
/// Media related individual python methods for our module.
|
||||
class PythonMethodsMedia {
|
||||
public:
|
||||
static PyMethodDef methods_def[];
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_MEDIA_H_
|
||||
610
src/ballistica/python/methods/python_methods_networking.cc
Normal file
610
src/ballistica/python/methods/python_methods_networking.cc
Normal file
@ -0,0 +1,610 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/methods/python_methods_networking.h"
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/app/app_globals.h"
|
||||
#include "ballistica/game/connection/connection_to_host.h"
|
||||
#include "ballistica/game/game.h"
|
||||
#include "ballistica/math/vector3f.h"
|
||||
#include "ballistica/networking/master_server_config.h"
|
||||
#include "ballistica/networking/network_reader.h"
|
||||
#include "ballistica/networking/networking.h"
|
||||
#include "ballistica/networking/sockaddr.h"
|
||||
#include "ballistica/networking/telnet_server.h"
|
||||
#include "ballistica/platform/platform.h"
|
||||
#include "ballistica/python/python.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Ignore signed bitwise stuff; python macros do it quite a bit.
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "hicpp-signed-bitwise"
|
||||
|
||||
auto PyGetPublicPartyEnabled(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("getpublicpartyenabled");
|
||||
static const char* kwlist[] = {nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
|
||||
const_cast<char**>(kwlist)))
|
||||
return nullptr;
|
||||
assert(g_python);
|
||||
if (g_game->public_party_enabled()) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PySetPublicPartyEnabled(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("setpublicpartyenabled");
|
||||
int enable;
|
||||
static const char* kwlist[] = {"enabled", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "p",
|
||||
const_cast<char**>(kwlist), &enable)) {
|
||||
return nullptr;
|
||||
}
|
||||
assert(g_python);
|
||||
g_game->SetPublicPartyEnabled(static_cast<bool>(enable));
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PySetPublicPartyName(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("setpublicpartyname");
|
||||
PyObject* name_obj;
|
||||
static const char* kwlist[] = {"name", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O",
|
||||
const_cast<char**>(kwlist), &name_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
std::string name = Python::GetPyString(name_obj);
|
||||
assert(g_python);
|
||||
g_game->SetPublicPartyName(name);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PySetPublicPartyStatsURL(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("setpublicpartystatsurl");
|
||||
PyObject* url_obj;
|
||||
static const char* kwlist[] = {"url", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O",
|
||||
const_cast<char**>(kwlist), &url_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
// The call expects an empty string for the no-url option.
|
||||
std::string url = (url_obj == Py_None) ? "" : Python::GetPyString(url_obj);
|
||||
assert(g_python);
|
||||
g_game->SetPublicPartyStatsURL(url);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetPublicPartyMaxSize(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("getpublicpartymaxsize");
|
||||
static const char* kwlist[] = {nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
|
||||
const_cast<char**>(kwlist))) {
|
||||
return nullptr;
|
||||
}
|
||||
assert(g_python);
|
||||
return PyLong_FromLong(g_game->public_party_max_size());
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PySetPublicPartyMaxSize(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("setpublicpartymaxsize");
|
||||
int max_size;
|
||||
static const char* kwlist[] = {"max_size", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "i",
|
||||
const_cast<char**>(kwlist), &max_size)) {
|
||||
return nullptr;
|
||||
}
|
||||
assert(g_python);
|
||||
g_game->SetPublicPartyMaxSize(max_size);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PySetAuthenticateClients(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("set_authenticate_clients");
|
||||
int enable;
|
||||
static const char* kwlist[] = {"enable", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "p",
|
||||
const_cast<char**>(kwlist), &enable)) {
|
||||
return nullptr;
|
||||
}
|
||||
assert(g_game);
|
||||
g_game->set_require_client_authentication(static_cast<bool>(enable));
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PySetAdmins(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("set_admins");
|
||||
PyObject* admins_obj;
|
||||
static const char* kwlist[] = {"admins", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O",
|
||||
const_cast<char**>(kwlist), &admins_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
assert(g_game);
|
||||
|
||||
auto admins = Python::GetPyStrings(admins_obj);
|
||||
std::set<std::string> adminset;
|
||||
for (auto&& admin : admins) {
|
||||
adminset.insert(admin);
|
||||
}
|
||||
g_game->set_admin_public_ids(adminset);
|
||||
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PySetEnableDefaultKickVoting(PyObject* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("set_enable_default_kick_voting");
|
||||
int enable;
|
||||
static const char* kwlist[] = {"enable", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "p",
|
||||
const_cast<char**>(kwlist), &enable)) {
|
||||
return nullptr;
|
||||
}
|
||||
assert(g_game);
|
||||
g_game->set_kick_voting_enabled(static_cast<bool>(enable));
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyConnectToParty(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("connect_to_party");
|
||||
std::string address;
|
||||
PyObject* address_obj;
|
||||
int port = kDefaultPort;
|
||||
|
||||
// Whether we should print standard 'connecting...' and 'party full..'
|
||||
// messages when false, only odd errors such as version incompatibility will
|
||||
// be printed and most connection attempts will be silent todo: could
|
||||
// generalize this to pass all results to a callback instead
|
||||
int print_progress = 1;
|
||||
static const char* kwlist[] = {"address", "port", "print_progress", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|ip",
|
||||
const_cast<char**>(kwlist), &address_obj,
|
||||
&port, &print_progress)) {
|
||||
return nullptr;
|
||||
}
|
||||
address = Python::GetPyString(address_obj);
|
||||
|
||||
// Disallow in headless build (people were using this for spam-bots).
|
||||
|
||||
if (HeadlessMode()) {
|
||||
throw Exception("Not available in headless mode.");
|
||||
}
|
||||
|
||||
SockAddr s;
|
||||
try {
|
||||
s = SockAddr(address, port);
|
||||
|
||||
// HACK: CLion currently flags our catch clause as unreachable even
|
||||
// though SockAddr constructor can throw exceptions. Work around that here.
|
||||
if (explicit_bool(false)) {
|
||||
throw Exception();
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
ScreenMessage(g_game->GetResourceString("invalidAddressErrorText"),
|
||||
{1, 0, 0});
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
g_game->PushHostConnectedUDPCall(s, static_cast<bool>(print_progress));
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyAcceptPartyInvitation(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("accept_party_invitation");
|
||||
const char* invite_id;
|
||||
static const char* kwlist[] = {"invite_id", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "s",
|
||||
const_cast<char**>(kwlist), &invite_id)) {
|
||||
return nullptr;
|
||||
}
|
||||
g_platform->AndroidGPGSPartyInviteAccept(invite_id);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetGooglePlayPartyClientCount(PyObject* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_google_play_party_client_count");
|
||||
BA_PRECONDITION(InGameThread());
|
||||
#if BA_GOOGLE_BUILD
|
||||
return PyLong_FromLong(g_game->GetGooglePlayClientCount());
|
||||
#else
|
||||
return PyLong_FromLong(0);
|
||||
#endif
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyClientInfoQueryResponse(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("client_info_query_response");
|
||||
const char* token;
|
||||
PyObject* response_obj;
|
||||
static const char* kwlist[] = {"token", "response", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "sO",
|
||||
const_cast<char**>(kwlist), &token,
|
||||
&response_obj)) {
|
||||
return nullptr;
|
||||
}
|
||||
g_game->SetClientInfoFromMasterServer(token, response_obj);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetConnectionToHostInfo(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_connection_to_host_info");
|
||||
static const char* kwlist[] = {nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
|
||||
const_cast<char**>(kwlist))) {
|
||||
return nullptr;
|
||||
}
|
||||
ConnectionToHost* hc = g_game->connection_to_host();
|
||||
if (hc) {
|
||||
return Py_BuildValue("{sssi}", "name", hc->party_name().c_str(),
|
||||
"build_number", hc->build_number());
|
||||
} else {
|
||||
return Py_BuildValue("{}");
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyDisconnectFromHost(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("disconnect_from_host");
|
||||
static const char* kwlist[] = {nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
|
||||
const_cast<char**>(kwlist))) {
|
||||
return nullptr;
|
||||
}
|
||||
g_game->PushDisconnectFromHostCall();
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyDisconnectClient(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("disconnect_client");
|
||||
int client_id;
|
||||
int ban_time = 300; // Old default before we exposed this.
|
||||
static const char* kwlist[] = {"client_id", "ban_time", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "i|i",
|
||||
const_cast<char**>(kwlist), &client_id,
|
||||
&ban_time)) {
|
||||
return nullptr;
|
||||
}
|
||||
bool kickable = g_game->DisconnectClient(client_id, ban_time);
|
||||
if (kickable) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetGamePort(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_game_port");
|
||||
int port = 0;
|
||||
if (g_network_reader != nullptr) {
|
||||
// hmmm; we're just fetching the ipv4 port here;
|
||||
// 6 could be different....
|
||||
port = g_network_reader->port4();
|
||||
}
|
||||
return Py_BuildValue("i", port);
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyGetMasterServerAddress(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_master_server_address");
|
||||
int source = -1; // use default..
|
||||
if (!PyArg_ParseTuple(args, "|i", &source)) {
|
||||
return nullptr;
|
||||
}
|
||||
// source -1 implies to use current one
|
||||
if (source == -1) {
|
||||
source = g_app_globals->master_server_source;
|
||||
}
|
||||
const char* addr;
|
||||
if (source == 0) {
|
||||
addr = BA_MASTER_SERVER_DEFAULT_ADDR;
|
||||
} else if (source == 1) {
|
||||
addr = BA_MASTER_SERVER_FALLBACK_ADDR;
|
||||
} else {
|
||||
BA_LOG_ONCE("Error: Got unexpected source: " + std::to_string(source)
|
||||
+ ".");
|
||||
addr = BA_MASTER_SERVER_FALLBACK_ADDR;
|
||||
}
|
||||
return PyUnicode_FromString(addr);
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PySetMasterServerSource(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("set_master_server_source");
|
||||
int source;
|
||||
if (!PyArg_ParseTuple(args, "i", &source)) return nullptr;
|
||||
if (source != 0 && source != 1) {
|
||||
BA_LOG_ONCE("Error: Invalid server source: " + std::to_string(source)
|
||||
+ ".");
|
||||
source = 1;
|
||||
}
|
||||
g_app_globals->master_server_source = source;
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PySetTelnetAccessEnabled(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("set_telnet_access_enabled");
|
||||
assert(InGameThread());
|
||||
int enable;
|
||||
static const char* kwlist[] = {"enable", nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "p",
|
||||
const_cast<char**>(kwlist), &enable)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (g_app_globals->telnet_server) {
|
||||
g_app_globals->telnet_server->SetAccessEnabled(static_cast<bool>(enable));
|
||||
} else {
|
||||
throw Exception("Telnet server not enabled.");
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyHostScanCycle(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("host_scan_cycle");
|
||||
g_networking->HostScanCycle();
|
||||
std::vector<Networking::ScanResultsEntry> results =
|
||||
g_networking->GetScanResults();
|
||||
PyObject* py_list = PyList_New(0);
|
||||
for (auto&& i : results) {
|
||||
PyList_Append(py_list, Py_BuildValue("{ssss}", "display_string",
|
||||
i.display_string.c_str(), "address",
|
||||
i.address.c_str()));
|
||||
}
|
||||
return py_list;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyEndHostScanning(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("end_host_scanning");
|
||||
g_networking->EndHostScanning();
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyHaveConnectedClients(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("have_connected_clients");
|
||||
if (g_game->GetConnectedClientCount() > 0) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PyInvitePlayers(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("invite_players");
|
||||
static const char* kwlist[] = {nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
|
||||
const_cast<char**>(kwlist))) {
|
||||
return nullptr;
|
||||
}
|
||||
g_platform->AndroidGPGSPartyInvitePlayers();
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
PyMethodDef PythonMethodsNetworking::methods_def[] = {
|
||||
{"invite_players", (PyCFunction)PyInvitePlayers,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"invite_players() -> None\n"
|
||||
"\n"
|
||||
"(internal)"
|
||||
"\n"
|
||||
"Category: General Utility Functions"},
|
||||
|
||||
{"have_connected_clients", (PyCFunction)PyHaveConnectedClients,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"have_connected_clients() -> bool\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Category: General Utility Functions"},
|
||||
|
||||
{"end_host_scanning", (PyCFunction)PyEndHostScanning,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"end_host_scanning() -> None\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Category: General Utility Functions"},
|
||||
|
||||
{"host_scan_cycle", (PyCFunction)PyHostScanCycle,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"host_scan_cycle() -> list\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"set_telnet_access_enabled", (PyCFunction)PySetTelnetAccessEnabled,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"set_telnet_access_enabled(enable: bool)\n"
|
||||
" -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"set_master_server_source", PySetMasterServerSource, METH_VARARGS,
|
||||
"set_master_server_source(source: int) -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"get_master_server_address", PyGetMasterServerAddress, METH_VARARGS,
|
||||
"get_master_server_address(source: int = -1) -> str\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Return the address of the master server."},
|
||||
|
||||
{"get_game_port", PyGetGamePort, METH_VARARGS,
|
||||
"get_game_port() -> int\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Return the port ballistica is hosting on."},
|
||||
|
||||
{"disconnect_from_host", (PyCFunction)PyDisconnectFromHost,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"disconnect_from_host() -> None\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Category: General Utility Functions"},
|
||||
|
||||
{"disconnect_client", (PyCFunction)PyDisconnectClient,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"disconnect_client(client_id: int, ban_time: int = 300) -> bool\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"get_connection_to_host_info", (PyCFunction)PyGetConnectionToHostInfo,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"get_connection_to_host_info() -> dict\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"client_info_query_response", (PyCFunction)PyClientInfoQueryResponse,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"client_info_query_response(token: str, response: Any) -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"get_google_play_party_client_count",
|
||||
(PyCFunction)PyGetGooglePlayPartyClientCount, METH_VARARGS | METH_KEYWORDS,
|
||||
"get_google_play_party_client_count() -> int\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"accept_party_invitation", (PyCFunction)PyAcceptPartyInvitation,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"accept_party_invitation(invite_id: str) -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"connect_to_party", (PyCFunction)PyConnectToParty,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"connect_to_party(address: str, port: int = None,\n"
|
||||
" print_progress: bool = True) -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"set_authenticate_clients", (PyCFunction)PySetAuthenticateClients,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"set_authenticate_clients(enable: bool) -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"set_admins", (PyCFunction)PySetAdmins, METH_VARARGS | METH_KEYWORDS,
|
||||
"set_admins(admins: List[str]) -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"set_enable_default_kick_voting",
|
||||
(PyCFunction)PySetEnableDefaultKickVoting, METH_VARARGS | METH_KEYWORDS,
|
||||
"set_enable_default_kick_voting(enable: bool) -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"set_public_party_max_size", (PyCFunction)PySetPublicPartyMaxSize,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"set_public_party_max_size(max_size: int) -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"get_public_party_max_size", (PyCFunction)PyGetPublicPartyMaxSize,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"get_public_party_max_size() -> int\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"set_public_party_stats_url", (PyCFunction)PySetPublicPartyStatsURL,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"set_public_party_stats_url(url: Optional[str]) -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"set_public_party_name", (PyCFunction)PySetPublicPartyName,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"set_public_party_name(name: str) -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"set_public_party_enabled", (PyCFunction)PySetPublicPartyEnabled,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"set_public_party_enabled(enabled: bool) -> None\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{"get_public_party_enabled", (PyCFunction)PyGetPublicPartyEnabled,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"get_public_party_enabled() -> bool\n"
|
||||
"\n"
|
||||
"(internal)"},
|
||||
|
||||
{nullptr, nullptr, 0, nullptr}};
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
} // namespace ballistica
|
||||
18
src/ballistica/python/methods/python_methods_networking.h
Normal file
18
src/ballistica/python/methods/python_methods_networking.h
Normal file
@ -0,0 +1,18 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_NETWORKING_H_
|
||||
#define BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_NETWORKING_H_
|
||||
|
||||
#include "ballistica/python/python_sys.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
/// Networking related individual python methods for our module.
|
||||
class PythonMethodsNetworking {
|
||||
public:
|
||||
static PyMethodDef methods_def[];
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_NETWORKING_H_
|
||||
1031
src/ballistica/python/methods/python_methods_system.cc
Normal file
1031
src/ballistica/python/methods/python_methods_system.cc
Normal file
File diff suppressed because it is too large
Load Diff
18
src/ballistica/python/methods/python_methods_system.h
Normal file
18
src/ballistica/python/methods/python_methods_system.h
Normal file
@ -0,0 +1,18 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_SYSTEM_H_
|
||||
#define BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_SYSTEM_H_
|
||||
|
||||
#include "ballistica/python/python_sys.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
/// System related individual python methods for our module.
|
||||
class PythonMethodsSystem {
|
||||
public:
|
||||
static PyMethodDef methods_def[];
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_SYSTEM_H_
|
||||
2710
src/ballistica/python/methods/python_methods_ui.cc
Normal file
2710
src/ballistica/python/methods/python_methods_ui.cc
Normal file
File diff suppressed because it is too large
Load Diff
18
src/ballistica/python/methods/python_methods_ui.h
Normal file
18
src/ballistica/python/methods/python_methods_ui.h
Normal file
@ -0,0 +1,18 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_UI_H_
|
||||
#define BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_UI_H_
|
||||
|
||||
#include "ballistica/python/python_sys.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
/// UI related individual python methods for our module.
|
||||
class PythonMethodsUI {
|
||||
public:
|
||||
static PyMethodDef methods_def[];
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_UI_H_
|
||||
420
src/ballistica/python/python.h
Normal file
420
src/ballistica/python/python.h
Normal file
@ -0,0 +1,420 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_PYTHON_H_
|
||||
#define BALLISTICA_PYTHON_PYTHON_H_
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
#include "ballistica/core/context.h"
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/generic/buffer.h"
|
||||
#include "ballistica/generic/runnable.h"
|
||||
#include "ballistica/math/point2d.h"
|
||||
#include "ballistica/platform/min_sdl.h"
|
||||
#include "ballistica/python/python_ref.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
/// General python support/infrastructure class.
|
||||
class Python {
|
||||
public:
|
||||
/// When calling a python callable directly, you can use the following
|
||||
/// to push and pop a text label which will be printed as 'call' in errors.
|
||||
class ScopedCallLabel {
|
||||
public:
|
||||
explicit ScopedCallLabel(const char* label) {
|
||||
prev_label_ = current_label_;
|
||||
}
|
||||
~ScopedCallLabel() { current_label_ = prev_label_; }
|
||||
static auto current_label() -> const char* { return current_label_; }
|
||||
|
||||
private:
|
||||
const char* prev_label_ = nullptr;
|
||||
static const char* current_label_;
|
||||
BA_DISALLOW_CLASS_COPIES(ScopedCallLabel);
|
||||
};
|
||||
|
||||
/// Use this to protect Python code that may be run in cases where we don't
|
||||
/// hold the Global Interpreter Lock (GIL) (basically anything outside of the
|
||||
/// game thread).
|
||||
class ScopedInterpreterLock {
|
||||
public:
|
||||
ScopedInterpreterLock();
|
||||
~ScopedInterpreterLock();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
Impl* impl_ = nullptr;
|
||||
};
|
||||
|
||||
/// Return whether the current thread holds the global-interpreter-lock.
|
||||
/// We must always hold the GIL while running python code.
|
||||
/// This *should* generally be the case by default, but this can be handy for
|
||||
/// sanity checking that.
|
||||
static auto HaveGIL() -> bool;
|
||||
|
||||
/// Attempt to print the python stack trace.
|
||||
static void PrintStackTrace();
|
||||
|
||||
/// Pass any PyObject* (including nullptr) to get a readable string
|
||||
/// (basically equivalent of str(foo)).
|
||||
static auto ObjToString(PyObject* obj) -> std::string;
|
||||
|
||||
/// Given an asset-package python object and a media name, verify
|
||||
/// that the asset-package is valid in the current context and return
|
||||
/// its fully qualified name if so. Throw an Exception if not.
|
||||
auto ValidatedPackageAssetName(PyObject* package, const char* name)
|
||||
-> std::string;
|
||||
|
||||
static void LogContextForCallableLabel(const char* label);
|
||||
static void LogContextEmpty();
|
||||
static void LogContextAuto();
|
||||
static void LogContextNonGameThread();
|
||||
Python();
|
||||
~Python();
|
||||
|
||||
void Reset(bool init = true);
|
||||
|
||||
auto GetContextBaseString() -> std::string;
|
||||
auto GetControllerValue(InputDevice* input_device,
|
||||
const std::string& value_name) -> int;
|
||||
auto GetControllerFloatValue(InputDevice* input_device,
|
||||
const std::string& value_name) -> float;
|
||||
void HandleDeviceMenuPress(InputDevice* input_device);
|
||||
auto GetLastPlayerNameFromInputDevice(InputDevice* input_device)
|
||||
-> std::string;
|
||||
void AcquireGIL();
|
||||
void ReleaseGIL();
|
||||
|
||||
void LaunchStringEdit(TextWidget* w);
|
||||
void CaptureGamePadInput(PyObject* obj);
|
||||
void ReleaseGamePadInput();
|
||||
void CaptureKeyboardInput(PyObject* obj);
|
||||
void ReleaseKeyboardInput();
|
||||
void HandleFriendScoresCB(const FriendScoreSet& ss);
|
||||
void IssueCallInGameThreadWarning(PyObject* call);
|
||||
|
||||
/// Borrowed from python's source code: used in overriding of objects' dir()
|
||||
/// results.
|
||||
static auto generic_dir(PyObject* self) -> PyObject*;
|
||||
|
||||
/// For use by g_game in passing events along to the python layer (for
|
||||
/// captured input, etc).
|
||||
auto HandleJoystickEvent(const SDL_Event& event,
|
||||
InputDevice* input_device = nullptr) -> bool;
|
||||
auto HandleKeyPressEvent(const SDL_Keysym& keysym) -> bool;
|
||||
auto HandleKeyReleaseEvent(const SDL_Keysym& keysym) -> bool;
|
||||
|
||||
auto inited() const -> bool { return inited_; }
|
||||
|
||||
/// Attempt to push the log to the server - returns true if no error occurs
|
||||
/// (note; this doesn't mean that the log actually made it; just that it got
|
||||
/// sent off)
|
||||
static auto PutLog(bool fatal, bool short_suicide_timer = true) -> bool;
|
||||
|
||||
/// Filter incoming chat message from client.
|
||||
/// If returns false, message should be ignored.
|
||||
auto FilterChatMessage(std::string* message, int client_id) -> bool;
|
||||
|
||||
/// Pass a chat message along to the python UI layer for handling..
|
||||
void HandleLocalChatMessage(const std::string& message);
|
||||
|
||||
void DispatchScoresToBeatResponse(
|
||||
bool success, const std::list<ScoreToBeat>& scores_to_beat,
|
||||
void* PyCallback);
|
||||
|
||||
/// Pop up an in-game window to show a url (NOT in a browser).
|
||||
void ShowURL(const std::string& url);
|
||||
|
||||
void AddCleanFrameCommand(const Object::Ref<PythonContextCall>& c);
|
||||
void RunCleanFrameCommands();
|
||||
|
||||
/// Return a minimal filename/position string such as 'foo.py:201' based
|
||||
/// on the python stack state. This shouldn't be too expensive to fetch and
|
||||
/// is useful as an object identifier/etc.
|
||||
static auto GetPythonFileLocation(bool pretty = true) -> std::string;
|
||||
|
||||
void PartyInvite(const std::string& player, const std::string& invite_id);
|
||||
void PartyInviteRevoke(const std::string& invite_id);
|
||||
void set_env_obj(PyObject* obj) { env_ = obj; }
|
||||
auto env_obj() const -> PyObject* {
|
||||
assert(env_);
|
||||
return env_;
|
||||
}
|
||||
auto main_dict() const -> PyObject* {
|
||||
assert(main_dict_);
|
||||
return main_dict_;
|
||||
}
|
||||
void PlayMusic(const std::string& music_type, bool continuous);
|
||||
|
||||
// Fetch raw values from the config dict. The default value is returned if
|
||||
// the requested value is not present or not of a compatible type.
|
||||
// Note: to get app config values you should generally use the bs::AppConfig
|
||||
// functions (which themselves call these functions)
|
||||
auto GetRawConfigValue(const char* name)
|
||||
-> PyObject*; // (returns a borrowed ref)
|
||||
auto GetRawConfigValue(const char* name, const char* default_value)
|
||||
-> std::string;
|
||||
auto GetRawConfigValue(const char* name, float default_value) -> float;
|
||||
auto GetRawConfigValue(const char* name, int default_value) -> int;
|
||||
auto GetRawConfigValue(const char* name, bool default_value) -> bool;
|
||||
void SetRawConfigValue(const char* name, float value);
|
||||
|
||||
void RunDeepLink(const std::string& url);
|
||||
auto GetResource(const char* key, const char* fallback_resource = nullptr,
|
||||
const char* fallback_value = nullptr) -> std::string;
|
||||
auto GetTranslation(const char* category, const char* s) -> std::string;
|
||||
|
||||
// For checking and pulling values out of python objects.
|
||||
// These will all throw Exceptions on errors.
|
||||
static auto GetPyString(PyObject* o) -> std::string;
|
||||
static auto GetPyInt64(PyObject* o) -> int64_t;
|
||||
static auto GetPyInt(PyObject* o) -> int;
|
||||
static auto GetPyNode(PyObject* o, bool allow_empty_ref = false,
|
||||
bool allow_none = false) -> Node*;
|
||||
static auto GetPyNodes(PyObject* o) -> std::vector<Node*>;
|
||||
static auto GetPyMaterials(PyObject* o) -> std::vector<Material*>;
|
||||
static auto GetPyTextures(PyObject* o) -> std::vector<Texture*>;
|
||||
static auto GetPyModels(PyObject* o) -> std::vector<Model*>;
|
||||
static auto GetPySounds(PyObject* o) -> std::vector<Sound*>;
|
||||
static auto GetPyCollideModels(PyObject* o) -> std::vector<CollideModel*>;
|
||||
static auto GetPyCollideModel(PyObject* o, bool allow_empty_ref = false,
|
||||
bool allow_none = false) -> CollideModel*;
|
||||
static auto IsPySession(PyObject* o) -> bool;
|
||||
static auto GetPySession(PyObject* o) -> Session*;
|
||||
static auto IsPyString(PyObject* o) -> bool;
|
||||
static auto GetPyBool(PyObject* o) -> bool;
|
||||
static auto GetPyHostActivity(PyObject* o) -> HostActivity*;
|
||||
static auto IsPyHostActivity(PyObject* o) -> bool;
|
||||
static auto GetPyInputDevice(PyObject* o) -> InputDevice*;
|
||||
static auto IsPyPlayer(PyObject* o) -> bool;
|
||||
static auto GetPyPlayer(PyObject* o, bool allow_empty_ref = false,
|
||||
bool allow_none = false) -> Player*;
|
||||
static auto GetPySessionPlayer(PyObject* o, bool allow_empty_ref = false,
|
||||
bool allow_none = false) -> Player*;
|
||||
static auto GetPyMaterial(PyObject* o, bool allow_empty_ref = false,
|
||||
bool allow_none = false) -> Material*;
|
||||
static auto GetPyTexture(PyObject* o, bool allow_empty_ref = false,
|
||||
bool allow_none = false) -> Texture*;
|
||||
static auto GetPyModel(PyObject* o, bool allow_empty_ref = false,
|
||||
bool allow_none = false) -> Model*;
|
||||
static auto GetPySound(PyObject* o, bool allow_empty_ref = false,
|
||||
bool allow_none = false) -> Sound*;
|
||||
static auto GetPyData(PyObject* o, bool allow_empty_ref = false,
|
||||
bool allow_none = false) -> Data*;
|
||||
static auto GetPyWidget(PyObject* o) -> Widget*;
|
||||
static auto CanGetPyDouble(PyObject* o) -> bool;
|
||||
static auto GetPyFloat(PyObject* o) -> float {
|
||||
return static_cast<float>(GetPyDouble(o));
|
||||
}
|
||||
static auto GetPyDouble(PyObject* o) -> double;
|
||||
static auto GetPyFloats(PyObject* o) -> std::vector<float>;
|
||||
static auto GetPyInts64(PyObject* o) -> std::vector<int64_t>;
|
||||
static auto GetPyInts(PyObject* o) -> std::vector<int>;
|
||||
static auto GetPyStrings(PyObject* o) -> std::vector<std::string>;
|
||||
static auto GetPyUInts64(PyObject* o) -> std::vector<uint64_t>;
|
||||
static auto GetPyPoint2D(PyObject* o) -> Point2D;
|
||||
static auto CanGetPyVector3f(PyObject* o) -> bool;
|
||||
static auto GetPyVector3f(PyObject* o) -> Vector3f;
|
||||
|
||||
static auto GetPyEnum_Permission(PyObject* obj) -> Permission;
|
||||
static auto GetPyEnum_SpecialChar(PyObject* obj) -> SpecialChar;
|
||||
static auto GetPyEnum_TimeType(PyObject* obj) -> TimeType;
|
||||
static auto GetPyEnum_TimeFormat(PyObject* obj) -> TimeFormat;
|
||||
static auto IsPyEnum_InputType(PyObject* obj) -> bool;
|
||||
static auto GetPyEnum_InputType(PyObject* obj) -> InputType;
|
||||
|
||||
static auto GetNodeAttr(Node* node, const char* attribute_name) -> PyObject*;
|
||||
static void SetNodeAttr(Node* node, const char* attr_name,
|
||||
PyObject* value_obj);
|
||||
|
||||
static void SetPythonException(PyExcType exctype, const char* description);
|
||||
|
||||
static void DoBuildNodeMessage(PyObject* args, int arg_offset,
|
||||
Buffer<char>* b, PyObject** user_message_obj);
|
||||
auto DoNewNode(PyObject* args, PyObject* keywds) -> Node*;
|
||||
|
||||
/// Identifiers for specific Python objects we grab references to for easy
|
||||
/// access.
|
||||
enum class ObjID {
|
||||
kEmptyTuple,
|
||||
kApp,
|
||||
kEnv,
|
||||
kDeepCopyCall,
|
||||
kShallowCopyCall,
|
||||
kShouldShatterMessageClass,
|
||||
kImpactDamageMessageClass,
|
||||
kPickedUpMessageClass,
|
||||
kDroppedMessageClass,
|
||||
kOutOfBoundsMessageClass,
|
||||
kPickUpMessageClass,
|
||||
kDropMessageClass,
|
||||
kShowURLWindowCall,
|
||||
kActivityClass,
|
||||
kSessionClass,
|
||||
kJsonDumpsCall,
|
||||
kJsonLoadsCall,
|
||||
kGetDeviceValueCall,
|
||||
kDeviceMenuPressCall,
|
||||
kGetLastPlayerNameFromInputDeviceCall,
|
||||
kOnScreenKeyboardClass,
|
||||
kFilterChatMessageCall,
|
||||
kHandleLocalChatMessageCall,
|
||||
kHandlePartyInviteCall,
|
||||
kHandlePartyInviteRevokeCall,
|
||||
kDoPlayMusicCall,
|
||||
kDeepLinkCall,
|
||||
kGetResourceCall,
|
||||
kTranslateCall,
|
||||
kLStrClass,
|
||||
kCallClass,
|
||||
kGarbageCollectCall,
|
||||
kConfig,
|
||||
kOnAppLaunchCall,
|
||||
kClientInfoQueryResponseCall,
|
||||
kResetToMainMenuCall,
|
||||
kSetConfigFullscreenOnCall,
|
||||
kSetConfigFullscreenOffCall,
|
||||
kNotSignedInScreenMessageCall,
|
||||
kConnectingToPartyMessageCall,
|
||||
kRejectingInviteAlreadyInPartyMessageCall,
|
||||
kConnectionFailedMessageCall,
|
||||
kTemporarilyUnavailableMessageCall,
|
||||
kInProgressMessageCall,
|
||||
kErrorMessageCall,
|
||||
kPurchaseNotValidErrorCall,
|
||||
kPurchaseAlreadyInProgressErrorCall,
|
||||
kGearVRControllerWarningCall,
|
||||
kVROrientationResetCBMessageCall,
|
||||
kVROrientationResetMessageCall,
|
||||
kHandleAppResumeCall,
|
||||
kHandleLogCall,
|
||||
kLaunchMainMenuSessionCall,
|
||||
kLanguageTestToggleCall,
|
||||
kAwardInControlAchievementCall,
|
||||
kAwardDualWieldingAchievementCall,
|
||||
kPrintCorruptFileErrorCall,
|
||||
kPlayGongSoundCall,
|
||||
kLaunchCoopGameCall,
|
||||
kPurchasesRestoredMessageCall,
|
||||
kDismissWiiRemotesWindowCall,
|
||||
kUnavailableMessageCall,
|
||||
kSubmitAnalyticsCountsCall,
|
||||
kSetLastAdNetworkCall,
|
||||
kNoGameCircleMessageCall,
|
||||
kEmptyCall,
|
||||
kLevelIconPressCall,
|
||||
kTrophyIconPressCall,
|
||||
kCoinIconPressCall,
|
||||
kTicketIconPressCall,
|
||||
kBackButtonPressCall,
|
||||
kFriendsButtonPressCall,
|
||||
kPrintTraceCall,
|
||||
kToggleFullscreenCall,
|
||||
kPartyIconActivateCall,
|
||||
kReadConfigCall,
|
||||
kUIRemotePressCall,
|
||||
kQuitWindowCall,
|
||||
kRemoveInGameAdsMessageCall,
|
||||
kTelnetAccessRequestCall,
|
||||
kOnAppPauseCall,
|
||||
kQuitCall,
|
||||
kShutdownCall,
|
||||
kGCDisableCall,
|
||||
kShowPostPurchaseMessageCall,
|
||||
kContextError,
|
||||
kNotFoundError,
|
||||
kNodeNotFoundError,
|
||||
kSessionTeamNotFoundError,
|
||||
kInputDeviceNotFoundError,
|
||||
kDelegateNotFoundError,
|
||||
kSessionPlayerNotFoundError,
|
||||
kWidgetNotFoundError,
|
||||
kActivityNotFoundError,
|
||||
kSessionNotFoundError,
|
||||
kAssetPackageClass,
|
||||
kTimeFormatClass,
|
||||
kTimeTypeClass,
|
||||
kInputTypeClass,
|
||||
kPermissionClass,
|
||||
kSpecialCharClass,
|
||||
kPlayerClass,
|
||||
kGetPlayerIconCall,
|
||||
kLstrFromJsonCall,
|
||||
kLast // Sentinel; must be at end.
|
||||
};
|
||||
|
||||
/// Access a particular Python object we've grabbed/stored.
|
||||
auto obj(ObjID id) const -> const PythonRef& {
|
||||
assert(id < ObjID::kLast);
|
||||
if (g_buildconfig.debug_build()) {
|
||||
if (!objs_[static_cast<int>(id)].exists()) {
|
||||
throw Exception("Python::obj() called on nonexistent val "
|
||||
+ std::to_string(static_cast<int>(id)));
|
||||
}
|
||||
}
|
||||
return objs_[static_cast<int>(id)];
|
||||
}
|
||||
|
||||
/// Return whether we have a particular Python object.
|
||||
auto objexists(ObjID id) const -> bool {
|
||||
assert(id < ObjID::kLast);
|
||||
return objs_[static_cast<int>(id)].exists();
|
||||
}
|
||||
|
||||
/// Push a call to a preset obj to the game thread
|
||||
/// (will be run in the UI context).
|
||||
void PushObjCall(ObjID obj);
|
||||
|
||||
/// Push a call with a single string arg.
|
||||
void PushObjCall(ObjID obj, const std::string& arg);
|
||||
|
||||
/// Register python location and returns true if it has not
|
||||
/// yet been registered. (for print-once type stuff).
|
||||
auto DoOnce() -> bool;
|
||||
|
||||
/// Check values passed to timer functions; triggers warnings
|
||||
/// for cases that look like they're passing milliseconds as seconds
|
||||
/// or vice versa... (can remove this once things are settled in).
|
||||
void TimeFormatCheck(TimeFormat time_format, PyObject* length_obj);
|
||||
|
||||
private:
|
||||
/// Check/set debug related initialization.
|
||||
void SetupInterpreterDebugState();
|
||||
|
||||
/// Set up system paths if needed (for embedded builds).
|
||||
void SetupPythonHome();
|
||||
|
||||
/// Set the value for a named object.
|
||||
void SetObj(ObjID id, PyObject* pyobj, bool incref = false);
|
||||
|
||||
/// Set the value for a named object and verify that it is a callable.
|
||||
void SetObjCallable(ObjID id, PyObject* pyobj, bool incref = false);
|
||||
|
||||
/// Set the value for a named object to the result of a Python expression.
|
||||
void SetObj(ObjID id, const char* expression);
|
||||
|
||||
/// Set the value for a named object to the result of a Python expression
|
||||
/// and verify that it is callable.
|
||||
void SetObjCallable(ObjID id, const char* expression);
|
||||
|
||||
std::set<std::string> do_once_locations_;
|
||||
PythonRef objs_[static_cast<int>(ObjID::kLast)];
|
||||
bool inited_{};
|
||||
std::list<Object::Ref<PythonContextCall> > clean_frame_commands_;
|
||||
PythonRef game_pad_call_;
|
||||
PythonRef keyboard_call_;
|
||||
PyObject* empty_dict_object_{};
|
||||
PyObject* main_dict_{};
|
||||
PyObject* env_{};
|
||||
PyThreadState* thread_state_{};
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_PYTHON_H_
|
||||
202
src/ballistica/python/python_command.cc
Normal file
202
src/ballistica/python/python_command.cc
Normal file
@ -0,0 +1,202 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/python_command.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "ballistica/python/python.h"
|
||||
#include "ballistica/python/python_sys.h"
|
||||
|
||||
// Save/restore current command for logging/etc.
|
||||
// this isn't exception-safe, but we should never let
|
||||
// exceptions bubble up through python api calls anyway
|
||||
// or we'll have bigger problems on our hands.
|
||||
#define PUSH_PYCOMMAND(OBJ) \
|
||||
PythonCommand* prev_pycmd = current_command_; \
|
||||
current_command_ = OBJ
|
||||
#define POP_PYCOMMAND() current_command_ = prev_pycmd
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
PythonCommand* PythonCommand::current_command_ = nullptr;
|
||||
|
||||
PythonCommand::PythonCommand() = default;
|
||||
|
||||
PythonCommand::PythonCommand(std::string command_in)
|
||||
: command_(std::move(command_in)) {}
|
||||
|
||||
PythonCommand::PythonCommand(std::string command_in, std::string file_name_in)
|
||||
: command_(std::move(command_in)), file_name_(std::move(file_name_in)) {}
|
||||
|
||||
PythonCommand::PythonCommand(const PythonCommand& c) : command_(c.command_) {}
|
||||
|
||||
auto PythonCommand::operator=(const PythonCommand& src) -> PythonCommand& {
|
||||
if (&src == this) {
|
||||
return *this;
|
||||
}
|
||||
file_code_obj_.Release();
|
||||
eval_code_obj_.Release();
|
||||
command_ = src.command_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto PythonCommand::operator=(const std::string& src) -> PythonCommand& {
|
||||
file_code_obj_.Release();
|
||||
eval_code_obj_.Release();
|
||||
command_ = src;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void PythonCommand::CompileForExec() {
|
||||
assert(Python::HaveGIL());
|
||||
assert(file_code_obj_.get() == nullptr);
|
||||
PyObject* o =
|
||||
Py_CompileString(command_.c_str(), file_name_.c_str(), Py_file_input);
|
||||
if (o == nullptr) {
|
||||
// we pass zero here to avoid grabbing references to this exception
|
||||
// which can cause objects to stick around and trip up our deletion checks
|
||||
// (nodes, actors existing after their games have ended)
|
||||
PyErr_PrintEx(0);
|
||||
} else {
|
||||
file_code_obj_.Acquire(o);
|
||||
}
|
||||
}
|
||||
|
||||
void PythonCommand::CompileForEval(bool print_errors) {
|
||||
assert(Python::HaveGIL());
|
||||
assert(eval_code_obj_.get() == nullptr);
|
||||
PyObject* o =
|
||||
Py_CompileString(command_.c_str(), file_name_.c_str(), Py_eval_input);
|
||||
if (o == nullptr) {
|
||||
if (print_errors) {
|
||||
// we pass zero here to avoid grabbing references to this exception
|
||||
// which can cause objects to stick around and trip up our deletion checks
|
||||
// (nodes, actors existing after their games have ended)
|
||||
PyErr_PrintEx(0);
|
||||
}
|
||||
PyErr_Clear();
|
||||
} else {
|
||||
eval_code_obj_.Acquire(o);
|
||||
}
|
||||
}
|
||||
|
||||
PythonCommand::~PythonCommand() { dead_ = true; }
|
||||
|
||||
auto PythonCommand::Run() -> bool {
|
||||
assert(Python::HaveGIL());
|
||||
if (!g_python) {
|
||||
// This probably means the game is dying; let's not
|
||||
// throw an exception here so we don't mask the original error.
|
||||
Log("PythonCommand: not running due to null g_python");
|
||||
return false;
|
||||
}
|
||||
assert(!dead_);
|
||||
if (!file_code_obj_.get()) {
|
||||
CompileForExec();
|
||||
assert(!dead_);
|
||||
}
|
||||
if (file_code_obj_.get()) {
|
||||
PUSH_PYCOMMAND(this);
|
||||
PyObject* v = PyEval_EvalCode(file_code_obj_.get(), g_python->main_dict(),
|
||||
g_python->main_dict());
|
||||
POP_PYCOMMAND();
|
||||
|
||||
// Technically the python call could have killed us;
|
||||
// make sure that didn't happen.
|
||||
assert(!dead_);
|
||||
if (v == nullptr) {
|
||||
// Save/restore error or it can mess with context print calls.
|
||||
BA_PYTHON_ERROR_SAVE;
|
||||
Log("ERROR: exception in Python call:");
|
||||
LogContext();
|
||||
BA_PYTHON_ERROR_RESTORE;
|
||||
|
||||
// We pass zero here to avoid grabbing references to this exception
|
||||
// which can cause objects to stick around and trip up our deletion
|
||||
// checks (nodes, actors existing after their games have ended).
|
||||
PyErr_PrintEx(0);
|
||||
PyErr_Clear();
|
||||
} else {
|
||||
Py_DECREF(v);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto PythonCommand::CanEval() -> bool {
|
||||
assert(Python::HaveGIL());
|
||||
assert(g_python);
|
||||
if (!eval_code_obj_.get()) {
|
||||
CompileForEval(false);
|
||||
}
|
||||
if (!eval_code_obj_.get()) {
|
||||
PyErr_Clear();
|
||||
return false;
|
||||
}
|
||||
PyErr_Clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto PythonCommand::RunReturnObj(bool print_errors) -> PyObject* {
|
||||
assert(Python::HaveGIL());
|
||||
assert(g_python);
|
||||
assert(!dead_);
|
||||
if (!eval_code_obj_.get()) {
|
||||
CompileForEval(print_errors);
|
||||
assert(!dead_);
|
||||
}
|
||||
if (!eval_code_obj_.get()) {
|
||||
if (print_errors) {
|
||||
// Save/restore error or it can mess with context print calls.
|
||||
BA_PYTHON_ERROR_SAVE;
|
||||
Log("ERROR: exception in Python call:");
|
||||
LogContext();
|
||||
BA_PYTHON_ERROR_RESTORE;
|
||||
// We pass zero here to avoid grabbing references to this exception
|
||||
// which can cause objects to stick around and trip up our deletion checks
|
||||
// (nodes, actors existing after their games have ended)
|
||||
PyErr_PrintEx(0);
|
||||
}
|
||||
|
||||
// Consider the python error handled at this point.
|
||||
// If C++ land wants to throw an exception or whatnot based on this result,
|
||||
// that's a totally different thing.
|
||||
PyErr_Clear();
|
||||
return nullptr;
|
||||
}
|
||||
PUSH_PYCOMMAND(this);
|
||||
PyObject* v = PyEval_EvalCode(eval_code_obj_.get(), g_python->main_dict(),
|
||||
g_python->main_dict());
|
||||
POP_PYCOMMAND();
|
||||
assert(!dead_);
|
||||
if (v == nullptr) {
|
||||
if (print_errors) {
|
||||
// save/restore error or it can mess with context print calls
|
||||
BA_PYTHON_ERROR_SAVE;
|
||||
Log("ERROR: exception in Python call:");
|
||||
LogContext();
|
||||
BA_PYTHON_ERROR_RESTORE;
|
||||
// we pass zero here to avoid grabbing references to this exception
|
||||
// which can cause objects to stick around and trip up our deletion checks
|
||||
// (nodes, actors existing after their games have ended)
|
||||
PyErr_PrintEx(0);
|
||||
}
|
||||
|
||||
// Consider the python error handled at this point.
|
||||
// If C++ land wants to throw an exception or whatnot based on this result,
|
||||
// that's a totally different thing.
|
||||
PyErr_Clear();
|
||||
return nullptr;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
void PythonCommand::LogContext() {
|
||||
assert(Python::HaveGIL());
|
||||
std::string s = std::string(" call: ") + command();
|
||||
s += g_python->GetContextBaseString();
|
||||
Log(s);
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
73
src/ballistica/python/python_command.h
Normal file
73
src/ballistica/python/python_command.h
Normal file
@ -0,0 +1,73 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_PYTHON_COMMAND_H_
|
||||
#define BALLISTICA_PYTHON_PYTHON_COMMAND_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
#include "ballistica/python/python_ref.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// String based python commands.
|
||||
// Does not save/restore context or anything;
|
||||
// for that functionality use PythonContextCall;
|
||||
|
||||
// Note to self: originally I though I'd be using this in a lot of places,
|
||||
// so I added the ability to compile once and run repeatedly, quietly capture
|
||||
// output instead of printing it, etc. Now, however, its usage is pretty
|
||||
// much limited to a few places such as handling stdin and the in-game console.
|
||||
// (Most places it is much cleaner to work with proper python modules and just
|
||||
// interact with PyObject* refs to them)
|
||||
// I should look and see if python's default high level calls would suffice
|
||||
// for these purposes and potentially kill this off.
|
||||
class PythonCommand {
|
||||
public:
|
||||
PythonCommand();
|
||||
PythonCommand(std::string command); // NOLINT (want to allow char*)
|
||||
|
||||
static auto current_command() -> PythonCommand* { return current_command_; }
|
||||
// file_name will be listed on error output
|
||||
PythonCommand(std::string command, std::string file_name);
|
||||
PythonCommand(const PythonCommand& other);
|
||||
|
||||
// copy a command
|
||||
auto operator=(const PythonCommand& other) -> PythonCommand&;
|
||||
|
||||
// set the command to a new command string
|
||||
auto operator=(const std::string& command) -> PythonCommand&;
|
||||
~PythonCommand();
|
||||
auto command() -> const std::string& { return command_; }
|
||||
|
||||
/// Run the command.
|
||||
/// return true if the command was successfully run
|
||||
/// (not to be confused with the command's result)
|
||||
/// This works for non-eval-able commands.
|
||||
auto Run() -> bool;
|
||||
|
||||
/// Run thecommand and return the result as a new Python reference.
|
||||
/// Only works for eval-able commands.
|
||||
/// Returns nullptr on errors, but Python error state will be cleared.
|
||||
auto RunReturnObj(bool print_errors = false) -> PyObject*;
|
||||
|
||||
void LogContext();
|
||||
|
||||
/// Return true if the command can be evaluated; otherwise it can only be
|
||||
/// executed
|
||||
auto CanEval() -> bool;
|
||||
void CompileForExec();
|
||||
void CompileForEval(bool print_errors);
|
||||
|
||||
private:
|
||||
bool dead_ = false;
|
||||
PythonRef file_code_obj_;
|
||||
PythonRef eval_code_obj_;
|
||||
std::string command_;
|
||||
std::string file_name_;
|
||||
static PythonCommand* current_command_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_PYTHON_COMMAND_H_
|
||||
135
src/ballistica/python/python_context_call.cc
Normal file
135
src/ballistica/python/python_context_call.cc
Normal file
@ -0,0 +1,135 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/python_context_call.h"
|
||||
|
||||
#include "ballistica/game/host_activity.h"
|
||||
#include "ballistica/game/session/host_session.h"
|
||||
#include "ballistica/python/python.h"
|
||||
#include "ballistica/python/python_sys.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// FIXME - should be static member var
|
||||
PythonContextCall* PythonContextCall::current_call_ = nullptr;
|
||||
|
||||
PythonContextCall::PythonContextCall(PyObject* obj_in) {
|
||||
assert(InGameThread());
|
||||
// as a sanity test, store the current context ptr just to make sure it
|
||||
// hasn't changed when we run
|
||||
#if BA_DEBUG_BUILD
|
||||
context_target_sanity_test_ = context_.target.get();
|
||||
#endif // BA_DEBUG_BUILD
|
||||
BA_PRECONDITION(PyCallable_Check(obj_in));
|
||||
object_.Acquire(obj_in);
|
||||
GetTrace();
|
||||
// ok now we need to register this call with whatever the context is;
|
||||
// it can be stored in a host-activity, a host-session, or the UI context.
|
||||
// whoever it is registered with will explicitly release its contents on
|
||||
// shutdown and ensure that nothing gets run after that point.
|
||||
if (HostActivity* ha = context_.GetHostActivity()) {
|
||||
ha->RegisterCall(this);
|
||||
} else if (HostSession* hs = context_.GetHostSession()) {
|
||||
hs->RegisterCall(this);
|
||||
} else if (context_.GetUIContext()) {
|
||||
// UI context never currently dies so no registering necessary here..
|
||||
} else {
|
||||
throw Exception(
|
||||
"Invalid context; ContextCalls must be created in a non-expired "
|
||||
"Activity, Session, or UI context. (call obj = "
|
||||
+ Python::ObjToString(obj_in) + ").",
|
||||
PyExcType::kContext);
|
||||
}
|
||||
}
|
||||
|
||||
PythonContextCall::~PythonContextCall() {
|
||||
// lets set up context while we take our stuff down
|
||||
// (we may be holding refs to actors or whatnot)
|
||||
ScopedSetContext cp(context_);
|
||||
object_.Release();
|
||||
}
|
||||
|
||||
auto PythonContextCall::GetObjectDescription() const -> std::string {
|
||||
return "<PythonContextCall from " + file_loc_ + " at "
|
||||
+ Utils::PtrToString(this) + ">";
|
||||
}
|
||||
|
||||
void PythonContextCall::GetTrace() {
|
||||
PyFrameObject* f = PyThreadState_GET()->frame;
|
||||
if (f) {
|
||||
// grab the file/line now in case we error
|
||||
// (useful for debugging simple timers and callbacks and such)
|
||||
file_loc_ = Python::GetPythonFileLocation();
|
||||
}
|
||||
}
|
||||
|
||||
// called by our owning context when it goes down
|
||||
// we should clear ourself out to be a no-op if we still happen to be called
|
||||
void PythonContextCall::MarkDead() {
|
||||
dead_ = true;
|
||||
object_.Release();
|
||||
}
|
||||
|
||||
void PythonContextCall::Run(PyObject* args) {
|
||||
assert(this);
|
||||
|
||||
if (!g_python) {
|
||||
// This probably means the game is dying; let's not
|
||||
// throw an exception here so we don't mask the original error.
|
||||
Log("PythonCommand: not running due to null g_python");
|
||||
return;
|
||||
}
|
||||
|
||||
if (dead_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sanity test: make sure our context didn't go away.
|
||||
#if BA_DEBUG_BUILD
|
||||
if (context_.target.get() != context_target_sanity_test_) {
|
||||
Log("WARNING: running Call after it's context has died: " + object_.Str());
|
||||
}
|
||||
#endif // BA_DEBUG_BUILD
|
||||
|
||||
// Restore the context from when we were made.
|
||||
ScopedSetContext cp(context_);
|
||||
|
||||
// Hold a ref to this call throughout this process
|
||||
// so we know it'll still exist if we need to report
|
||||
// exception info and whatnot.
|
||||
Object::Ref<PythonContextCall> keep_alive_ref(this);
|
||||
|
||||
PythonContextCall* prev_call = current_call_;
|
||||
current_call_ = this;
|
||||
assert(Python::HaveGIL());
|
||||
PyObject* o = PyObject_Call(
|
||||
object_.get(),
|
||||
args ? args : g_python->obj(Python::ObjID::kEmptyTuple).get(), nullptr);
|
||||
current_call_ = prev_call;
|
||||
|
||||
if (o) {
|
||||
Py_DECREF(o);
|
||||
} else {
|
||||
// Save/restore python error or it can mess with context print calls.
|
||||
BA_PYTHON_ERROR_SAVE;
|
||||
|
||||
Log("ERROR: exception in Python call:");
|
||||
LogContext();
|
||||
BA_PYTHON_ERROR_RESTORE;
|
||||
|
||||
// We pass zero here to avoid grabbing references to this exception
|
||||
// which can cause objects to stick around and trip up our deletion checks.
|
||||
// (nodes, actors existing after their games have ended).
|
||||
PyErr_PrintEx(0);
|
||||
PyErr_Clear();
|
||||
}
|
||||
}
|
||||
|
||||
void PythonContextCall::LogContext() {
|
||||
assert(InGameThread());
|
||||
std::string s = std::string(" root call: ") + object().Str();
|
||||
s += ("\n root call origin: " + file_loc());
|
||||
s += g_python->GetContextBaseString();
|
||||
Log(s);
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
54
src/ballistica/python/python_context_call.h
Normal file
54
src/ballistica/python/python_context_call.h
Normal file
@ -0,0 +1,54 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_PYTHON_CONTEXT_CALL_H_
|
||||
#define BALLISTICA_PYTHON_PYTHON_CONTEXT_CALL_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/core/context.h"
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/python/python_ref.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// A callable and context-state wrapped up in a convenient package.
|
||||
// Handy for use with user-submitted callbacks, as it restores context
|
||||
// state from when it was created and prints various useful bits of info
|
||||
// on exceptions.
|
||||
class PythonContextCall : public Object {
|
||||
public:
|
||||
static auto current_call() -> PythonContextCall* { return current_call_; }
|
||||
PythonContextCall() = default;
|
||||
~PythonContextCall() override;
|
||||
|
||||
/// Initialize from either a single callable object, or a tuple with a
|
||||
/// callable and optionally args and keywords
|
||||
explicit PythonContextCall(PyObject* callable);
|
||||
void Run(PyObject* args = nullptr);
|
||||
void Run(const PythonRef& args) { Run(args.get()); }
|
||||
auto Exists() const -> bool { return object_.exists(); }
|
||||
auto GetObjectDescription() const -> std::string override;
|
||||
void MarkDead();
|
||||
auto object() const -> const PythonRef& { return object_; }
|
||||
auto file_loc() const -> const std::string& { return file_loc_; }
|
||||
void LogContext();
|
||||
|
||||
private:
|
||||
void GetTrace(); // we try to grab basic trace info
|
||||
std::string file_loc_;
|
||||
int line_{};
|
||||
bool dead_ = false;
|
||||
PythonRef object_;
|
||||
Context context_;
|
||||
#if BA_DEBUG_BUILD
|
||||
ContextTarget* context_target_sanity_test_{};
|
||||
#endif
|
||||
static PythonContextCall* current_call_;
|
||||
};
|
||||
|
||||
// FIXME: this should be static member var
|
||||
extern PythonContextCall* g_current_python_call;
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_PYTHON_CONTEXT_CALL_H_
|
||||
25
src/ballistica/python/python_context_call_runnable.h
Normal file
25
src/ballistica/python/python_context_call_runnable.h
Normal file
@ -0,0 +1,25 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_PYTHON_CONTEXT_CALL_RUNNABLE_H_
|
||||
#define BALLISTICA_PYTHON_PYTHON_CONTEXT_CALL_RUNNABLE_H_
|
||||
|
||||
#include "ballistica/python/python_context_call.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// a simple runnable that stores and runs a python context call
|
||||
class PythonContextCallRunnable : public Runnable {
|
||||
public:
|
||||
explicit PythonContextCallRunnable(PyObject* o)
|
||||
: call(Object::New<PythonContextCall>(o)) {}
|
||||
Object::Ref<PythonContextCall> call;
|
||||
void Run() override {
|
||||
assert(call.exists());
|
||||
call->Run();
|
||||
}
|
||||
virtual ~PythonContextCallRunnable() = default;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_PYTHON_CONTEXT_CALL_RUNNABLE_H_
|
||||
185
src/ballistica/python/python_ref.cc
Normal file
185
src/ballistica/python/python_ref.cc
Normal file
@ -0,0 +1,185 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/python/python_ref.h"
|
||||
|
||||
#include "ballistica/math/vector2f.h"
|
||||
#include "ballistica/python/python.h"
|
||||
#include "ballistica/python/python_sys.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Ignore a few things that python macros do.
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "RedundantCast"
|
||||
|
||||
PythonRef::PythonRef(PyObject* obj_in, ReferenceBehavior b) {
|
||||
assert(g_python);
|
||||
assert(Python::HaveGIL());
|
||||
switch (b) {
|
||||
case kSteal:
|
||||
Steal(obj_in);
|
||||
break;
|
||||
case kStealWeak:
|
||||
if (obj_in) {
|
||||
Steal(obj_in);
|
||||
}
|
||||
break;
|
||||
case kAcquire:
|
||||
Acquire(obj_in);
|
||||
break;
|
||||
case kAcquireWeak:
|
||||
if (obj_in) {
|
||||
Acquire(obj_in);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PythonRef::Acquire(PyObject* obj_in) {
|
||||
BA_PRECONDITION(obj_in);
|
||||
assert(g_python);
|
||||
assert(Python::HaveGIL());
|
||||
|
||||
// Assign and increment the new one before decrementing our old
|
||||
// (in case its the same one or prev gets deallocated and accesses us
|
||||
// somehow).
|
||||
PyObject* prev = obj_;
|
||||
Py_INCREF(obj_in);
|
||||
obj_ = obj_in;
|
||||
if (prev) {
|
||||
Py_DECREF(prev);
|
||||
}
|
||||
}
|
||||
|
||||
void PythonRef::Steal(PyObject* obj_in) {
|
||||
BA_PRECONDITION(obj_in);
|
||||
assert(g_python);
|
||||
assert(Python::HaveGIL());
|
||||
|
||||
// Assign before decrementing the old
|
||||
// (in case prev gets deallocated and accesses us somehow).
|
||||
PyObject* prev = obj_;
|
||||
obj_ = obj_in;
|
||||
if (prev) {
|
||||
Py_DECREF(prev);
|
||||
}
|
||||
}
|
||||
|
||||
void PythonRef::Release() {
|
||||
assert(g_python);
|
||||
assert(Python::HaveGIL());
|
||||
|
||||
// Py_CLEAR uses a temp variable and assigns o to nullptr first
|
||||
// so we're safe if the clear triggers something that (again) releases or
|
||||
// destroys us.
|
||||
if (obj_) {
|
||||
Py_CLEAR(obj_);
|
||||
}
|
||||
}
|
||||
|
||||
auto PythonRef::Str() const -> std::string {
|
||||
assert(Python::HaveGIL());
|
||||
if (!obj_) {
|
||||
return "<nullptr PyObject>";
|
||||
}
|
||||
PyObject* obj = PyObject_Str(obj_);
|
||||
if (!obj) {
|
||||
return "<error fetching python obj as string>";
|
||||
}
|
||||
PythonRef s(obj, PythonRef::kSteal);
|
||||
assert(PyUnicode_Check(obj)); // NOLINT (signed with bitwise)
|
||||
return PyUnicode_AsUTF8(s.get());
|
||||
}
|
||||
|
||||
auto PythonRef::Repr() const -> std::string {
|
||||
assert(Python::HaveGIL());
|
||||
BA_PRECONDITION(obj_);
|
||||
PythonRef s(PyObject_Repr(obj_), PythonRef::kSteal);
|
||||
assert(PyUnicode_Check(s.get())); // NOLINT (signed with bitwise)
|
||||
return PyUnicode_AsUTF8(s.get());
|
||||
}
|
||||
|
||||
auto PythonRef::ValueAsString() const -> std::string {
|
||||
assert(Python::HaveGIL());
|
||||
BA_PRECONDITION(obj_);
|
||||
return Python::GetPyString(obj_);
|
||||
}
|
||||
|
||||
auto PythonRef::ValueAsInt() const -> int64_t {
|
||||
assert(Python::HaveGIL());
|
||||
BA_PRECONDITION(obj_);
|
||||
return Python::GetPyInt64(obj_);
|
||||
}
|
||||
|
||||
auto PythonRef::GetAttr(const char* name) const -> PythonRef {
|
||||
assert(g_python);
|
||||
assert(Python::HaveGIL());
|
||||
BA_PRECONDITION(obj_);
|
||||
PyObject* val = PyObject_GetAttrString(get(), name);
|
||||
if (!val) {
|
||||
PyErr_Clear();
|
||||
throw Exception("Attribute not found: '" + std::string(name) + "'.",
|
||||
PyExcType::kAttribute);
|
||||
}
|
||||
return PythonRef(val, PythonRef::kSteal);
|
||||
}
|
||||
|
||||
auto PythonRef::NewRef() const -> PyObject* {
|
||||
assert(Python::HaveGIL());
|
||||
if (obj_ == nullptr) {
|
||||
throw Exception("PythonRef::NewRef() called with nullptr obj_");
|
||||
}
|
||||
Py_INCREF(obj_);
|
||||
return obj_;
|
||||
}
|
||||
|
||||
auto PythonRef::CallableCheck() const -> bool {
|
||||
BA_PRECONDITION(obj_);
|
||||
assert(Python::HaveGIL());
|
||||
return static_cast<bool>(PyCallable_Check(obj_));
|
||||
}
|
||||
|
||||
auto PythonRef::Call(PyObject* args, PyObject* keywds, bool print_errors) const
|
||||
-> PythonRef {
|
||||
assert(obj_);
|
||||
assert(g_python);
|
||||
assert(Python::HaveGIL());
|
||||
assert(CallableCheck());
|
||||
assert(args);
|
||||
assert(PyTuple_Check(args)); // NOLINT (signed bitwise stuff)
|
||||
assert(!keywds || PyDict_Check(keywds)); // NOLINT (signed bitwise)
|
||||
PyObject* out = PyObject_Call(obj_, args, keywds);
|
||||
if (!out) {
|
||||
if (print_errors) {
|
||||
// Save/restore error or it can mess with context print calls.
|
||||
BA_PYTHON_ERROR_SAVE;
|
||||
Log("ERROR: exception in Python call:");
|
||||
Python::LogContextAuto();
|
||||
BA_PYTHON_ERROR_RESTORE;
|
||||
|
||||
// We pass zero here to avoid grabbing references to this exception
|
||||
// which can cause objects to stick around and trip up our deletion checks
|
||||
// (nodes, actors existing after their games have ended).
|
||||
PyErr_PrintEx(0);
|
||||
}
|
||||
PyErr_Clear();
|
||||
}
|
||||
return out ? PythonRef(out, PythonRef::kSteal) : PythonRef();
|
||||
}
|
||||
|
||||
auto PythonRef::Call() const -> PythonRef {
|
||||
return Call(g_python->obj(Python::ObjID::kEmptyTuple).get());
|
||||
}
|
||||
|
||||
auto PythonRef::Call(const Vector2f& val) const -> PythonRef {
|
||||
assert(Python::HaveGIL());
|
||||
PythonRef args(Py_BuildValue("((ff))", val.x, val.y), PythonRef::kSteal);
|
||||
return Call(args);
|
||||
}
|
||||
|
||||
PythonRef::~PythonRef() { Release(); }
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
} // namespace ballistica
|
||||
130
src/ballistica/python/python_ref.h
Normal file
130
src/ballistica/python/python_ref.h
Normal file
@ -0,0 +1,130 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_PYTHON_REF_H_
|
||||
#define BALLISTICA_PYTHON_PYTHON_REF_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
/// A simple managed Python object reference.
|
||||
class PythonRef {
|
||||
public:
|
||||
/// Defines referencing behavior when creating new instances.
|
||||
enum ReferenceBehavior {
|
||||
/// Steal the provided object reference (and throw an Exception if it is
|
||||
/// nullptr).
|
||||
kSteal,
|
||||
/// Steal the provided object reference or set as unreferenced if it is
|
||||
/// nullptr.
|
||||
kStealWeak,
|
||||
/// Acquire a new reference to the provided object (and throw an Exception
|
||||
/// if it is nullptr).
|
||||
kAcquire,
|
||||
/// Acquire a new reference to the provided object or set as unreferenced if
|
||||
/// it is nullptr.
|
||||
kAcquireWeak
|
||||
};
|
||||
|
||||
/// Creates in an unreferenced state.
|
||||
PythonRef() {} // NOLINT (using '= default' here errors)
|
||||
|
||||
/// See ReferenceBehavior docs.
|
||||
PythonRef(PyObject* other, ReferenceBehavior behavior);
|
||||
|
||||
/// Copy constructor acquires a new reference (or sets as unreferenced)
|
||||
/// depending on other.
|
||||
PythonRef(const PythonRef& other) { *this = other; }
|
||||
virtual ~PythonRef();
|
||||
|
||||
/// Assignment from another PythonRef acquires a reference to the object
|
||||
/// referenced by other if there is one. If other has no reference, any
|
||||
/// reference of ours is cleared to match.
|
||||
auto operator=(const PythonRef& other) -> PythonRef& {
|
||||
assert(this != &other); // Shouldn't be self-assigning.
|
||||
if (other.exists()) {
|
||||
Acquire(other.get());
|
||||
} else {
|
||||
Release();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Comparing to another PythonRef does a pointer comparison
|
||||
/// (so basically the 'is' keyword in Python).
|
||||
/// Note that two unreferenced PythonRefs will be equal.
|
||||
auto operator==(const PythonRef& other) const -> bool {
|
||||
return (get() == other.get());
|
||||
}
|
||||
auto operator!=(const PythonRef& other) const -> bool {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
/// Acquire a new reference to the passed object. Throws an exception if
|
||||
/// nullptr is passed.
|
||||
void Acquire(PyObject* obj);
|
||||
|
||||
/// Steal the passed reference. Throws an Exception if nullptr is passed.
|
||||
void Steal(PyObject* obj);
|
||||
|
||||
/// Release the held reference (if one is held).
|
||||
void Release();
|
||||
|
||||
/// Clear the ref without decrementing its count and return the raw PyObject*
|
||||
auto HandOver() -> PyObject* {
|
||||
assert(obj_);
|
||||
PyObject* obj = obj_;
|
||||
obj_ = nullptr;
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// Return the underlying PyObject pointer.
|
||||
auto get() const -> PyObject* { return obj_; }
|
||||
|
||||
/// Increment the ref-count for the underlying PyObject and return it as a
|
||||
/// pointer.
|
||||
auto NewRef() const -> PyObject*;
|
||||
|
||||
/// Return whether we are pointing to a PyObject.
|
||||
auto exists() const -> bool { return obj_ != nullptr; }
|
||||
|
||||
/// Return a ref to an attribute on our PyObject or throw an Exception.
|
||||
auto GetAttr(const char* name) const -> PythonRef;
|
||||
|
||||
/// The equivalent of calling python str() on the contained PyObject.
|
||||
auto Str() const -> std::string;
|
||||
|
||||
/// The equivalent of calling repr() on the contained PyObject.
|
||||
auto Repr() const -> std::string;
|
||||
|
||||
/// For unicode, string, and ba.Lstr types, returns a utf8 string.
|
||||
/// Throws an exception for other types.
|
||||
auto ValueAsString() const -> std::string;
|
||||
auto ValueAsInt() const -> int64_t;
|
||||
|
||||
/// Returns whether the underlying PyObject is callable.
|
||||
auto CallableCheck() const -> bool;
|
||||
|
||||
/// Call the PyObject. On error, (optionally) prints errors and returns empty
|
||||
/// ref.
|
||||
auto Call(PyObject* args, PyObject* keywds = nullptr,
|
||||
bool print_errors = true) const -> PythonRef;
|
||||
auto Call(const PythonRef& args, const PythonRef& keywds = PythonRef(),
|
||||
bool print_errors = true) const -> PythonRef {
|
||||
return Call(args.get(), keywds.get(), print_errors);
|
||||
}
|
||||
auto Call() const -> PythonRef;
|
||||
|
||||
/// Call with various args..
|
||||
auto Call(const Vector2f& val) const
|
||||
-> PythonRef; // (val will be passed as tuple)
|
||||
|
||||
private:
|
||||
PyObject* obj_{};
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_PYTHON_PYTHON_REF_H_
|
||||
84
src/ballistica/python/python_sys.h
Normal file
84
src/ballistica/python/python_sys.h
Normal file
@ -0,0 +1,84 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_PYTHON_PYTHON_SYS_H_
|
||||
#define BALLISTICA_PYTHON_PYTHON_SYS_H_
|
||||
|
||||
// Any code that actually runs any python logic should include this.
|
||||
// This header pulls in the actual python includes and also defines some handy
|
||||
// macros and functions for working with python objects.
|
||||
|
||||
// This is the ONE place we actually include python.
|
||||
#include <Python.h>
|
||||
#include <frameobject.h>
|
||||
#include <weakrefobject.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
// Saving/restoring Python error state; useful when function PyObject_Str()
|
||||
// or other functionality is needed during error reporting; by default it
|
||||
// craps out when an error is set.
|
||||
#define BA_PYTHON_ERROR_SAVE \
|
||||
PyObject* pes_perr = nullptr; \
|
||||
PyObject* pes_pvalue = nullptr; \
|
||||
PyObject* pes_ptraceback = nullptr; \
|
||||
PyErr_Fetch(&pes_perr, &pes_pvalue, &pes_ptraceback)
|
||||
|
||||
#define BA_PYTHON_ERROR_RESTORE \
|
||||
PyErr_Restore(pes_perr, pes_pvalue, pes_ptraceback)
|
||||
|
||||
// Some macros to handle/propagate C++ exceptions within Python calls.
|
||||
#define BA_PYTHON_TRY \
|
||||
try { \
|
||||
((void)0)
|
||||
|
||||
// Set Python error state based on the caught C++ exception and returns null.
|
||||
#define BA_PYTHON_CATCH \
|
||||
} \
|
||||
catch (const Exception& e) { \
|
||||
e.SetPyError(); \
|
||||
return nullptr; \
|
||||
} \
|
||||
catch (const std::exception& e) { \
|
||||
PyErr_SetString(PyExc_RuntimeError, GetShortExceptionDescription(e)); \
|
||||
return nullptr; \
|
||||
} \
|
||||
((void)0)
|
||||
|
||||
// For use in tp_new; sets Python err, frees aborted self, returns null.
|
||||
#define BA_PYTHON_NEW_CATCH \
|
||||
} \
|
||||
catch (const Exception& e) { \
|
||||
e.SetPyError(); \
|
||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self)); \
|
||||
return nullptr; \
|
||||
} \
|
||||
catch (const std::exception& e) { \
|
||||
PyErr_SetString(PyExc_RuntimeError, GetShortExceptionDescription(e)); \
|
||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self)); \
|
||||
return nullptr; \
|
||||
} \
|
||||
((void)0)
|
||||
|
||||
// For use in tp_dealloc; simply prints the error.
|
||||
#define BA_PYTHON_DEALLOC_CATCH \
|
||||
} \
|
||||
catch (const std::exception& e) { \
|
||||
Log(std::string("Error: tp_dealloc exception: ") \
|
||||
+ GetShortExceptionDescription(e)); \
|
||||
} \
|
||||
((void)0)
|
||||
|
||||
// Sets Python error and returns -1.
|
||||
#define BA_PYTHON_INT_CATCH \
|
||||
} \
|
||||
catch (const Exception& e) { \
|
||||
e.SetPyError(); \
|
||||
return -1; \
|
||||
} \
|
||||
catch (const std::exception& e) { \
|
||||
PyErr_SetString(PyExc_RuntimeError, GetShortExceptionDescription(e)); \
|
||||
return -1; \
|
||||
} \
|
||||
((void)0)
|
||||
|
||||
#endif // BALLISTICA_PYTHON_PYTHON_SYS_H_
|
||||
@ -30,10 +30,10 @@ class PipRequirement:
|
||||
|
||||
PIP_REQUIREMENTS = [
|
||||
PipRequirement(modulename='pylint', minversion=[2, 6, 0]),
|
||||
PipRequirement(modulename='mypy', minversion=[0, 782]),
|
||||
PipRequirement(modulename='mypy', minversion=[0, 790]),
|
||||
PipRequirement(modulename='yapf', minversion=[0, 30, 0]),
|
||||
PipRequirement(modulename='cpplint', minversion=[1, 5, 4]),
|
||||
PipRequirement(modulename='pytest', minversion=[6, 0, 2]),
|
||||
PipRequirement(modulename='pytest', minversion=[6, 1, 1]),
|
||||
PipRequirement(modulename='typing_extensions'),
|
||||
PipRequirement(modulename='pytz'),
|
||||
PipRequirement(modulename='yaml', pipname='PyYAML'),
|
||||
|
||||
@ -306,3 +306,45 @@ def make_hash(obj: Any) -> int:
|
||||
# NOTE: there is sorted works correctly because it compares only
|
||||
# unique first values (i.e. dict keys)
|
||||
return hash(tuple(frozenset(sorted(new_obj.items()))))
|
||||
|
||||
|
||||
def asserttype(obj: Any, typ: Type[T]) -> T:
|
||||
"""Return an object typed as a given type.
|
||||
|
||||
Assert is used to check its actual type, so only use this when
|
||||
failures are not expected. Otherwise use checktype.
|
||||
"""
|
||||
assert isinstance(obj, typ)
|
||||
return obj
|
||||
|
||||
|
||||
def checktype(obj: Any, typ: Type[T]) -> T:
|
||||
"""Return an object typed as a given type.
|
||||
|
||||
Always checks the type at runtime with isinstance and throws a TypeError
|
||||
on failure. Use asserttype for more efficient (but less safe) equivalent.
|
||||
"""
|
||||
if not isinstance(obj, typ):
|
||||
raise TypeError(f'Expected a {typ}; got a {type(obj)}.')
|
||||
return obj
|
||||
|
||||
|
||||
def assert_non_optional(obj: Optional[T]) -> T:
|
||||
"""Return an object with Optional typing removed.
|
||||
|
||||
Assert is used to check its actual type, so only use this when
|
||||
failures are not expected. Use check_non_optional otherwise.
|
||||
"""
|
||||
assert obj is not None
|
||||
return obj
|
||||
|
||||
|
||||
def check_non_optional(obj: Optional[T]) -> T:
|
||||
"""Return an object with Optional typing removed.
|
||||
|
||||
Always checks the actual type and throws a TypeError on failure.
|
||||
Use assert_non_optional for a more efficient (but less safe) equivalent.
|
||||
"""
|
||||
if obj is None:
|
||||
raise TypeError('Got None value in check_non_optional.')
|
||||
return obj
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user