Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
SCR_SpawnLogic.c
Go to the documentation of this file.
2{
4 SAVE,
6}
7
8//------------------------------------------------------------------------------------------------
9/*
10 Authority:
11 Object responsible for defining respawn logic.
12
13 This object receives callbacks from parent SCR_RespawnSystemComponent that can be used
14 to either spawn the player on the authority side or just notify the remote player that
15 they can process to spawn, or any combination based on derived implementations.
16*/
17[BaseContainerProps(category: "Respawn")]
18class SCR_SpawnLogic
20 [Attribute(SCR_ESpawnLogicDisconnectBehaviour.SAVE.ToString(), UIWidgets.ComboBox, "Decide what happens to playercontroller persistence data on disconnect.", enums: ParamEnumArray.FromEnum(SCR_ESpawnLogicDisconnectBehaviour))]
21 protected SCR_ESpawnLogicDisconnectBehaviour m_eDisconnectPlayerControllerBehaviour;
22
23 [Attribute(SCR_ESpawnLogicDisconnectBehaviour.SAVE.ToString(), UIWidgets.ComboBox, "Decide what happens to character persistence data on disconnect.", enums: ParamEnumArray.FromEnum(SCR_ESpawnLogicDisconnectBehaviour))]
24 protected SCR_ESpawnLogicDisconnectBehaviour m_eDisconnectCharacterBehaviour;
25
26 protected SCR_RespawnSystemComponent m_RespawnSystem;
27
28 protected SCR_PersistenceSystem m_Persistence;
29 protected PersistenceCollection m_PlayerCollection;
30 protected PersistenceCollection m_CharacterCollection;
31 protected ref array<ref Tuple3<SCR_PlayerController, UUID, UUID>> m_aStoredControlledEntityIds = {};
32 protected ref map<int, UUID> m_mPendingPosessions = new map<int, UUID>();
33
34 #ifdef WORKBENCH
35 protected static vector s_vPlayFromCameraPos;
36 protected static vector s_vPlayFromCameraYpr;
37 #endif
38
39 //------------------------------------------------------------------------------------------------
40 void OnInit(SCR_RespawnSystemComponent owner)
41 {
42 m_RespawnSystem = owner;
43 auto gameMode = SCR_BaseGameMode.Cast(GetGame().GetGameMode());
44 m_Persistence = SCR_PersistenceSystem.GetByEntityWorld(gameMode);
45 if (m_Persistence)
46 SetupPersistenceCollections(owner);
47 }
48
49 //------------------------------------------------------------------------------------------------
51 protected void SetupPersistenceCollections(SCR_RespawnSystemComponent owner)
52 {
53 m_PlayerCollection = m_Persistence.FindCollection("Player");
54 m_CharacterCollection = m_Persistence.FindCollection("Character");
55 }
56
57 //------------------------------------------------------------------------------------------------
58 void OnPlayerRegistered_S(int playerId)
59 {
60 #ifdef _ENABLE_RESPAWN_LOGS
61 PrintFormat("%1::OnPlayerRegistered_S(playerId: %2)", Type().ToString(), playerId);
62 #endif
63
64 SCR_RespawnComponent respawnComponent = GetPlayerRespawnComponent_S(playerId);
65 respawnComponent.GetOnRespawnRequestInvoker_S().Insert(OnPlayerSpawnRequest_S);
66 respawnComponent.GetOnRespawnResponseInvoker_S().Insert(OnPlayerSpawnResponse_S);
67 }
68
69 //------------------------------------------------------------------------------------------------
70 void OnPlayerAuditSuccess_S(int playerId)
71 {
72 #ifdef _ENABLE_RESPAWN_LOGS
73 PrintFormat("%1::OnPlayerAuditSuccess_S(playerId: %2)", Type().ToString(), playerId);
74 #endif
75
76 if (!m_Persistence)
77 return;
78
79 // Assign players identity id to playercontroller in persistence tracking
80 const SCR_PlayerController playerController = SCR_PlayerController.Cast(GetGame().GetPlayerManager().GetPlayerController(playerId));
81 const UUID identity = SCR_PlayerIdentityUtils.GetPlayerIdentityId(playerId);
82 m_Persistence.SetId(playerController, identity);
83 }
84
85 //------------------------------------------------------------------------------------------------
86 void OnPlayerDisconnected_S(int playerId, KickCauseCode cause, int timeout)
87 {
88 #ifdef _ENABLE_RESPAWN_LOGS
89 PrintFormat("%1::OnPlayerDisconnected_S(playerId: %2)", Type().ToString(), playerId);
90 #endif
91
92 SCR_RespawnComponent respawnComponent = GetPlayerRespawnComponent_S(playerId);
93 respawnComponent.GetOnRespawnRequestInvoker_S().Remove(OnPlayerSpawnRequest_S);
94 respawnComponent.GetOnRespawnResponseInvoker_S().Remove(OnPlayerSpawnResponse_S);
95
96 if (!m_Persistence)
97 return;
98
99 auto playerController = SCR_PlayerController.Cast(GetGame().GetPlayerManager().GetPlayerController(playerId));
100 ForgetControlledEntityIds(playerController);
101
102 switch (m_eDisconnectPlayerControllerBehaviour)
103 {
105 {
106 // Save controller data and release tracking to ignore it being deleted when player manager is done with disconnect procedure.
107 m_Persistence.Save(playerController);
108 m_Persistence.StopTracking(playerController, false);
109 break;
110 }
111
113 {
114 m_Persistence.StopTracking(playerController);
115 break;
116 }
117 }
118 }
119
120 //------------------------------------------------------------------------------------------------
121 void OnPlayerSpawnRequest_S(SCR_SpawnRequestComponent requestComponent)
122 {
123 #ifdef _ENABLE_RESPAWN_LOGS
124 PrintFormat("%1::OnPlayerSpawnRequest_S(playerId: %2)", Type().ToString(), requestComponent.GetPlayerId());
125 #endif
126 }
127
128 //------------------------------------------------------------------------------------------------
129 void OnPlayerSpawnResponse_S(SCR_SpawnRequestComponent requestComponent, SCR_ESpawnResult response)
130 {
131 #ifdef _ENABLE_RESPAWN_LOGS
132 PrintFormat("%1::OnPlayerSpawnResponse_S(playerId: %2, response: %3)",
133 Type().ToString(), requestComponent.GetPlayerId(), typename.EnumToString(SCR_ESpawnResult, response));
134 #endif
135
136 if (response != SCR_ESpawnResult.OK)
137 OnPlayerSpawnFailed_S(requestComponent.GetPlayerId());
138 }
139
140 //------------------------------------------------------------------------------------------------
141 protected void OnPlayerSpawnFailed_S(int playerId)
142 {
143 }
144
145 //------------------------------------------------------------------------------------------------
146 void OnPlayerSpawned_S(int playerId, IEntity entity)
147 {
148 #ifdef _ENABLE_RESPAWN_LOGS
149 PrintFormat("%1::OnPlayerSpawned_S(playerId: %2, entity: %3)", Type().ToString(), playerId, entity);
150 #endif
151 }
152
153 //------------------------------------------------------------------------------------------------
154 void OnPlayerEntityChanged_S(int playerId, IEntity previousEntity, IEntity newEntity)
155 {
156 #ifdef _ENABLE_RESPAWN_LOGS
157 PrintFormat("%1::OnPlayerEntityChanged_S(playerId: %2, previousEntity: %3, newEntity: %4)",
158 Type().ToString(), playerId, previousEntity, newEntity);
159 #endif
160
161 if (!newEntity)
162 OnPlayerEntityLost_S(playerId);
163
164 if (!m_Persistence)
165 return;
166
167 // Check that the old entity does not count as player anymore
168 if (previousEntity)
169 m_Persistence.ReloadConfig(previousEntity);
170
171 // Refresh config to recongize what is a player and what might be a dead char.
172 if (newEntity)
173 m_Persistence.ReloadConfig(newEntity);
174
175 ApplyPendingPosession(playerId);
176 }
177
178 //------------------------------------------------------------------------------------------------
179 void OnPlayerKilled_S(int playerId, IEntity playerEntity, IEntity killerEntity, notnull Instigator killer)
180 {
181 #ifdef _ENABLE_RESPAWN_LOGS
182 PrintFormat("%1::OnPlayerKilled_S(playerId: %2, playerEntity: %3, killerEntity: %4, killerId: %5)",
183 Type().ToString(), playerId, playerEntity, killerEntity, killer.GetInstigatorPlayerID());
184 #endif
185
186 OnPlayerEntityLost_S(playerId);
187 }
188
189 //------------------------------------------------------------------------------------------------
190 void OnPlayerDeleted_S(int playerId, bool isDisconnect)
191 {
192 #ifdef _ENABLE_RESPAWN_LOGS
193 PrintFormat("%1::OnPlayerDeleted_S(playerId: %2, isDisconnect:%3)", Type().ToString(), playerId, isDisconnect);
194 #endif
195
196 if (!isDisconnect)
197 OnPlayerEntityLost_S(playerId);
198 }
199
200 //------------------------------------------------------------------------------------------------
201 void OnPlayerEntityCleanup_S(notnull IEntity playerEntity)
202 {
203 #ifdef _ENABLE_RESPAWN_LOGS
204 PrintFormat("%1::OnPlayerEntityCleanup_S(playerEntity:%2)", Type().ToString(), playerEntity);
205 #endif
206
207 if (!m_Persistence)
208 return;
209
210 switch (m_eDisconnectCharacterBehaviour)
211 {
213 {
214 // Save character data and release tracking to ignore it being deleted during player controller cleanup
215 m_Persistence.Save(playerEntity);
216 m_Persistence.StopTracking(playerEntity, false);
217 break;
218 }
219
221 {
222 m_Persistence.StopTracking(playerEntity);
223 break;
224 }
225 }
226 }
227
228 //------------------------------------------------------------------------------------------------
233 protected void OnPlayerEntityLost_S(int playerId)
234 {
235 #ifdef _ENABLE_RESPAWN_LOGS
236 PrintFormat("%1::OnPlayerEntityLost_S(playerId: %2)", Type().ToString(), playerId);
237 #endif
238 }
239
240 //------------------------------------------------------------------------------------------------
245 protected void NotifyPlayerReadyForSpawn(int playerId)
246 {
247 GetPlayerRespawnComponent_S(playerId).NotifyReadyForSpawn_S();
248 }
249
250 //------------------------------------------------------------------------------------------------
251 protected void ApplyPendingPosession(int playerId)
252 {
253 UUID posessCharacterId;
254 if (!m_mPendingPosessions.Take(playerId, posessCharacterId))
255 return;
256
257 Tuple2<int, bool> characterAvailableContext(playerId, false);
258 PersistenceWhenAvailableTask linkControlledEntityTask(OnControlledCharacterAvailable, characterAvailableContext);
259 m_Persistence.WhenAvailable(posessCharacterId, linkControlledEntityTask);
260 }
261
262 //------------------------------------------------------------------------------------------------
263 protected void ExcuteInitialLoadOrSpawn_S(int playerId)
264 {
265 #ifdef _ENABLE_RESPAWN_LOGS
266 PrintFormat("%1::ExcuteInitialLoadOrSpawn_S(playerId: %2)", Type().ToString(), playerId);
267 #endif
268
269 #ifdef WORKBENCH
270 // Wait one frame for inital play from camera entity to be available (or not).
271 GetGame().GetCallqueue().Call(RequestPlayerData_S, playerId);
272 #else
273 RequestPlayerData_S(playerId);
274 #endif
275 }
276
277 //------------------------------------------------------------------------------------------------
278 protected void RequestPlayerData_S(int playerId)
279 {
280 #ifdef _ENABLE_RESPAWN_LOGS
281 PrintFormat("%1::RequestPlayerData_S(playerId: %2)", Type().ToString(), playerId);
282 #endif
283
284 const PlayerController playerController = GetGame().GetPlayerManager().GetPlayerController(playerId);
285
286 #ifdef WORKBENCH
287 IEntity controlledEntity = playerController.GetControlledEntity();
288 if (controlledEntity)
289 {
290 s_vPlayFromCameraPos = controlledEntity.GetOrigin();
291 s_vPlayFromCameraYpr = controlledEntity.GetYawPitchRoll();
292 }
293 #endif
294
295 if (!m_Persistence || !m_CharacterCollection)
296 {
297 // No persistence data to request, proceed with spawning
298 OnPlayerDataLoaded_S(EPersistenceStatusCode.UNAVAILABLE, null, true, new Tuple1<PlayerController>(playerController));
299 return;
300 }
301
302 const EPersistenceSystemState state = m_Persistence.GetState();
303 if (state != EPersistenceSystemState.ACTIVE)
304 {
305 OnPlayerDataLoaded_S(EPersistenceStatusCode.UNAVAILABLE, null, true, new Tuple1<PlayerController>(playerController));
306 return;
307 }
308
309 // Load existing data about the player to see which character, faction, group etc to connect him with again on load
310 PersistenceLoadRequest request();
311 request.Instances = {playerController};
312 // Pass controller as weakptr via tuple
313 PersistenceResultCallback callback(OnPlayerDataLoaded_S, new Tuple1<PlayerController>(playerController));
314 m_Persistence.RequestLoad(request, callback);
315 }
316
317 //------------------------------------------------------------------------------------------------
318 protected void OnPlayerDataLoaded_S(EPersistenceStatusCode statusCode, Managed result, bool isLast, Managed context)
319 {
320 #ifdef _ENABLE_RESPAWN_LOGS
321 PrintFormat("%1::OnPlayerDataLoaded_S(%2, %3, %4)", Type().ToString(), typename.EnumToString(EPersistenceStatusCode, statusCode), result, context);
322 #endif
323
324 auto playerController = SCR_PlayerController.Cast(Tuple1<PlayerController>.Cast(context).param1);
325 if (!playerController)
326 return; // Response arrived after player already disconnected
327
328 const int playerId = playerController.GetPlayerId();
329 if (ResolveReconnection(playerId))
330 {
331 ForgetControlledEntityIds(playerController);
332 return; // User was reconnected, their entity was returned
333 }
334
335 if (statusCode != EPersistenceStatusCode.OK)
336 {
337 // Abort and proceed with default spawn
338 DoInitialSpawn_S(playerId);
339 return;
340 }
341
342 if (playerController != result)
343 return; // Something went terribly wrong.
344
345 UUID playerCharacterId, controlledCharacterId;
346 ConsumeControlledEntityIds(playerController, playerCharacterId, controlledCharacterId);
347
348 if (playerCharacterId.IsNull())
349 {
350 // Player did not have its own character, but posessed other entity (possible in e.g. GM)
351 if (!controlledCharacterId.IsNull())
352 {
353 Tuple2<int, bool> characterAvailableContext(playerController.GetPlayerId(), true);
354 PersistenceWhenAvailableTask linkControlledEntityTask(OnControlledCharacterAvailable, characterAvailableContext);
355 m_Persistence.WhenAvailable(controlledCharacterId, linkControlledEntityTask);
356 return;
357 }
358
359 // No player or posessed entity = abort and proceed with default spawn.
360 DoInitialSpawn_S(playerController.GetPlayerId());
361 return;
362 }
363
364 // Queue up posession of another entity after the main entity spawn has been completed
365 if (!controlledCharacterId.IsNull() && controlledCharacterId != playerCharacterId)
366 m_mPendingPosessions.Set(playerId, controlledCharacterId);
367
368 PersistenceSpawnRequest request();
369 request.Collection = m_CharacterCollection;
370 request.Include = {playerCharacterId};
371
372 Tuple1<int> playerCharContext(playerId);
373 PersistenceResultCallback callback(OnPlayerCharacterLoaded_S, playerCharContext);
374 m_Persistence.RequestSpawn(request, callback);
375 }
376
377 //------------------------------------------------------------------------------------------------
378 protected void OnPlayerCharacterLoaded_S(EPersistenceStatusCode statusCode, Managed result, bool isLast, Managed context)
379 {
380 #ifdef _ENABLE_RESPAWN_LOGS
381 PrintFormat("%1::OnPlayerCharacterLoaded_S(%2, %3, %4)", Type().ToString(), typename.EnumToString(EPersistenceStatusCode, statusCode), result, context);
382 #endif
383
384 auto playerDataContext = Tuple1<int>.Cast(context);
385
386 // Hand over
387 auto character = ChimeraCharacter.Cast(result);
388
389 // Apply play from camera pose to new char and delete the system spawned one, as we have our own from DB
390 // Also consume the saved data on first spawn back, afterwards save and load will use its own data.
391 #ifdef WORKBENCH
392 if (character)
393 {
394 bool needsChange;
395 vector transform[4];
396 character.GetWorldTransform(transform);
397
398 if (s_vPlayFromCameraPos != vector.Zero)
399 {
400 transform[3] = s_vPlayFromCameraPos;
401 needsChange = true;
402 s_vPlayFromCameraPos = vector.Zero;
403 }
404
405 if (s_vPlayFromCameraYpr != vector.Zero)
406 {
407 Math3D.AnglesToMatrix(s_vPlayFromCameraYpr, transform);
408 needsChange = true;
409 s_vPlayFromCameraYpr = vector.Zero;
410 }
411
412 if (needsChange)
413 character.Teleport(transform);
414 }
415
416 // Remove old player
417 PlayerController playerController = GetGame().GetPlayerManager().GetPlayerController(playerDataContext.param1);
418 IEntity controlledEntity = playerController.GetControlledEntity();
419 SCR_EntityHelper.DeleteEntityAndChildren(controlledEntity);
420 #endif
421
422 // Dead players will not work for respawn, as no events for additional death on them are raised after posession.
423 if (character && character.GetCharacterController().IsDead())
424 character = null;
425
426 // Check that we have a valid character to posess back
427 if (!character)
428 {
429 DoInitialSpawn_S(playerDataContext.param1);
430 return;
431 }
432
433 auto data = SCR_PossessSpawnData.FromEntity(character);
434 GetPlayerRespawnComponent_S(playerDataContext.param1).RequestSpawn(data);
435 }
436
437 //------------------------------------------------------------------------------------------------
438 protected void OnControlledCharacterAvailable(Managed instance, PersistenceDeferredDeserializeTask task, bool expired, Managed context)
439 {
440 auto characterAvailableContext = Tuple2<int, bool>.Cast(context);
441
442 auto entity = IEntity.Cast(instance);
443 if (entity)
444 {
445 // Check that the char is not dead by the time we hand over controls
446 auto charController = CharacterControllerComponent.Cast(entity.FindComponent(CharacterControllerComponent));
447 if (charController && charController.GetLifeState() != ECharacterLifeState.DEAD)
448 {
449 auto playerController = SCR_PlayerController.Cast(GetGame().GetPlayerManager().GetPlayerController(characterAvailableContext.param1));
450 playerController.SetPossessedEntity(entity);
451 return;
452 }
453 }
454
455 // No alive character was found during inital join process so proceed with default spawn
456 if (characterAvailableContext.param2)
457 DoInitialSpawn_S(characterAvailableContext.param1);
458 }
459
460 //------------------------------------------------------------------------------------------------
461 void StoreControlledEntityIds(notnull SCR_PlayerController playerController, UUID playerCharacterId, UUID controlledCharacterId)
462 {
463 m_aStoredControlledEntityIds.Insert(new Tuple3<SCR_PlayerController, UUID, UUID>(playerController, playerCharacterId, controlledCharacterId));
464 }
465
466 //------------------------------------------------------------------------------------------------
467 protected bool ConsumeControlledEntityIds(notnull SCR_PlayerController playerController, out UUID playerCharacterId, out UUID controlledCharacterId)
468 {
469 for (int i = 0, count = m_aStoredControlledEntityIds.Count(); i < count; i++)
470 {
471 if (m_aStoredControlledEntityIds[i].param1 == playerController)
472 {
473 playerCharacterId = m_aStoredControlledEntityIds[i].param2;
474 controlledCharacterId = m_aStoredControlledEntityIds[i].param3;
475 m_aStoredControlledEntityIds.Remove(i);
476 return true;
477 }
478 }
479
480 return false;
481 }
482
483 //------------------------------------------------------------------------------------------------
484 protected bool ForgetControlledEntityIds(notnull SCR_PlayerController playerController)
485 {
486 for (int i = 0, count = m_aStoredControlledEntityIds.Count(); i < count; i++)
487 {
488 if (m_aStoredControlledEntityIds[i].param1 == playerController)
489 {
490 m_aStoredControlledEntityIds.Remove(i);
491 return true;
492 }
493 }
494
495 return false;
496 }
497
498 //------------------------------------------------------------------------------------------------
499 protected void DoInitialSpawn_S(int playerId)
500 {
501 #ifdef _ENABLE_RESPAWN_LOGS
502 PrintFormat("%1::DoInitialSpawn_S(playerId: %2)", Type().ToString(), playerId);
503 #endif
504
505 #ifdef WORKBENCH
506 if (GetGame().GetPlayerController().GetPlayerId() == playerId && HandlePlayFromCamera())
507 return;
508 #endif
509
510 DoSpawn_S(playerId);
511 }
512
513 //------------------------------------------------------------------------------------------------
515 protected void DoSpawn_S(int playerId)
516 {
517 }
518
519 //------------------------------------------------------------------------------------------------
527 protected bool ResolveReconnection(int playerId)
528 {
529 #ifdef _ENABLE_RESPAWN_LOGS
530 PrintFormat("%1::ResolveReconnection(playerId: %2)", Type().ToString(), playerId);
531 #endif
532
533 SCR_ReconnectComponent reconnect = SCR_ReconnectComponent.GetInstance();
534 return reconnect && reconnect.HandlePlayerReconnect(playerId);
535 }
536
537 #ifdef WORKBENCH
538 //------------------------------------------------------------------------------------------------
539 protected bool HandlePlayFromCamera()
540 {
541 // Use play from camera entity if it is available
542 PlayerController playerController = GetGame().GetPlayerController();
543 IEntity controlledEntity = playerController.GetControlledEntity();
544 if (controlledEntity)
545 {
546 auto respawnSystem = SCR_RespawnSystemComponent.GetInstance();
547 if (respawnSystem)
548 respawnSystem.DestroyLoadingPlaceholder();
549
550 return true;
551 }
552
553 return false;
554 }
555 #endif
556
557 //------------------------------------------------------------------------------------------------
558 SCR_RespawnComponent GetPlayerRespawnComponent_S(int playerId)
559 {
560 return SCR_RespawnComponent.Cast(GetGame().GetPlayerManager().GetPlayerRespawnComponent(playerId));
561 }
562
563 //------------------------------------------------------------------------------------------------
564 SCR_RespawnComponent GetLocalPlayerRespawnComponent()
565 {
567 }
568
569 //------------------------------------------------------------------------------------------------
570 SCR_PlayerFactionAffiliationComponent GetPlayerFactionComponent_S(int playerId)
571 {
572 return SCR_PlayerFactionAffiliationComponent.Cast(GetGame().GetPlayerManager().GetPlayerController(playerId).FindComponent(SCR_PlayerFactionAffiliationComponent));
573 }
574
575 //------------------------------------------------------------------------------------------------
576 SCR_PlayerLoadoutComponent GetPlayerLoadoutComponent_S(int playerId)
577 {
578 return SCR_PlayerLoadoutComponent.Cast(GetGame().GetPlayerManager().GetPlayerController(playerId).FindComponent(SCR_PlayerLoadoutComponent));
579 }
580}
ArmaReforgerScripted GetGame()
Definition game.c:1398
SCR_AIAnimation_Loitering BaseContainerProps
Commanding menu commanding element class.
SCR_BaseGameMode GetGameMode()
SCR_ESpawnResult
Get all prefabs that have the spawner data
override void OnPlayerEntityChanged_S(int playerId, IEntity previousEntity, IEntity newEntity)
void SCR_RespawnComponent(IEntityComponentSource src, IEntity ent, IEntity parent)
NOTHING
SCR_ESpawnLogicDisconnectBehaviour
int Type
enum EVehicleType IEntity
proto external Managed FindComponent(typename typeName)
proto external vector GetOrigin()
proto external vector GetYawPitchRoll()
static sealed SCR_PersistenceSystem GetByEntityWorld(IEntity entity)
bool HandlePlayerReconnect(int playerId)
ECharacterLifeState
proto void PrintFormat(string fmt, void param1=NULL, void param2=NULL, void param3=NULL, void param4=NULL, void param5=NULL, void param6=NULL, void param7=NULL, void param8=NULL, void param9=NULL, LogLevel level=LogLevel.NORMAL)
SCR_FieldOfViewSettings Attribute
@ DELETE
Further load on the instance is aborted and is deleted immediately.
proto external PlayerController GetPlayerController()
proto external RespawnComponent GetRespawnComponent()
proto external int GetPlayerId()
EPersistenceStatusCode
EPersistenceSystemState
void Tuple3(T1 p1, T2 p2, T3 p3)
Definition tuple.c:95
void Tuple1(T1 p1)
Definition tuple.c:37
Tuple param1
proto external string ToString()
Plain C++ pointer, no weak pointers, no memory management.