1 /*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "core/components_ng/render/adapter/component_snapshot.h"
17
18 #include "transaction/rs_interfaces.h"
19 #include "base/log/ace_trace.h"
20 #include "bridge/common/utils/utils.h"
21 #include "core/components_ng/base/inspector.h"
22 #include "core/components_ng/pattern/image/image_pattern.h"
23 #include "core/components_ng/pattern/stack/stack_pattern.h"
24 #include "core/components_ng/render/adapter/rosen_render_context.h"
25
26 namespace OHOS::Ace::NG {
27 namespace {
28
29 constexpr std::chrono::duration<int, std::milli> SNAPSHOT_TIMEOUT_DURATION(3000);
30 constexpr std::chrono::duration<int, std::milli> CREATE_SNAPSHOT_TIMEOUT_DURATION(80);
31
32 class CustomizedCallback : public Rosen::SurfaceCaptureCallback {
33 public:
CustomizedCallback(ComponentSnapshot::JsCallback && jsCallback,WeakPtr<FrameNode> node)34 CustomizedCallback(ComponentSnapshot::JsCallback&& jsCallback, WeakPtr<FrameNode> node)
35 : callback_(std::move(jsCallback)), node_(std::move(node))
36 {}
37 ~CustomizedCallback() override = default;
OnSurfaceCapture(std::shared_ptr<Media::PixelMap> pixelMap)38 void OnSurfaceCapture(std::shared_ptr<Media::PixelMap> pixelMap) override
39 {
40 if (callback_ == nullptr) {
41 TAG_LOGW(AceLogTag::ACE_COMPONENT_SNAPSHOT, "Internal error! The callback_ is null");
42 auto node = node_.Upgrade();
43 CHECK_NULL_VOID(node);
44 Inspector::RemoveOffscreenNode(node);
45 return;
46 }
47 if (!pixelMap) {
48 TAG_LOGW(AceLogTag::ACE_COMPONENT_SNAPSHOT, "Internal error! The pixelmap returned by the system is null");
49 callback_(nullptr, ERROR_CODE_INTERNAL_ERROR, [node = node_]() {
50 auto frameNode = node.Upgrade();
51 CHECK_NULL_VOID(frameNode);
52 Inspector::RemoveOffscreenNode(frameNode);
53 });
54 } else {
55 TAG_LOGI(AceLogTag::ACE_COMPONENT_SNAPSHOT,
56 "ComponentSnapshot successful! pixelMap.width=%{public}d pixelMap.height=%{public}d",
57 pixelMap->GetWidth(), pixelMap->GetHeight());
58 callback_(pixelMap, ERROR_CODE_NO_ERROR, [node = node_]() {
59 auto frameNode = node.Upgrade();
60 CHECK_NULL_VOID(frameNode);
61 Inspector::RemoveOffscreenNode(frameNode);
62 });
63 }
64 }
65
66 private:
67 ComponentSnapshot::JsCallback callback_;
68 WeakPtr<FrameNode> node_;
69 };
70
71 class NormalCaptureCallback : public Rosen::SurfaceCaptureCallback {
72 public:
NormalCaptureCallback(ComponentSnapshot::NormalCallback && callback)73 explicit NormalCaptureCallback(ComponentSnapshot::NormalCallback&& callback) : callback_(std::move(callback)) {}
74 ~NormalCaptureCallback() override = default;
OnSurfaceCapture(std::shared_ptr<Media::PixelMap> pixelMap)75 void OnSurfaceCapture(std::shared_ptr<Media::PixelMap> pixelMap) override
76 {
77 CHECK_NULL_VOID(callback_);
78 callback_(pixelMap);
79 }
80
81 private:
82 ComponentSnapshot::NormalCallback callback_;
83 };
84
85 class SyncCustomizedCallback : public Rosen::SurfaceCaptureCallback {
86 public:
87 SyncCustomizedCallback() = default;
88 ~SyncCustomizedCallback() override = default;
OnSurfaceCapture(std::shared_ptr<Media::PixelMap> pixelMap)89 void OnSurfaceCapture(std::shared_ptr<Media::PixelMap> pixelMap) override
90 {
91 if (!pixelMap) {
92 TAG_LOGW(AceLogTag::ACE_COMPONENT_SNAPSHOT, "Internal error! The pixelmap returned by the system is null");
93 pixelMap_ = nullptr;
94 } else {
95 TAG_LOGI(AceLogTag::ACE_COMPONENT_SNAPSHOT,
96 "ComponentSnapshot successful! pixelMap.width=%{public}d pixelMap.height=%{public}d",
97 pixelMap->GetWidth(), pixelMap->GetHeight());
98 pixelMap_ = pixelMap;
99 }
100 std::unique_lock<std::mutex> lock(mutex_);
101 cv_.notify_all();
102 }
103
GetPixelMap(std::chrono::duration<int,std::milli> timeout)104 std::pair<int32_t, std::shared_ptr<Media::PixelMap>> GetPixelMap(std::chrono::duration<int, std::milli> timeout)
105 {
106 std::pair<int32_t, std::shared_ptr<Media::PixelMap>> result(ERROR_CODE_INTERNAL_ERROR, nullptr);
107 std::unique_lock<std::mutex> lock(mutex_);
108 auto status = cv_.wait_for(lock, timeout);
109 if (status == std::cv_status::timeout) {
110 return { ERROR_CODE_COMPONENT_SNAPSHOT_TIMEOUT, nullptr };
111 }
112 if (pixelMap_) {
113 result = { ERROR_CODE_NO_ERROR, pixelMap_ };
114 }
115 return result;
116 }
117
118 private:
119 mutable std::mutex mutex_;
120 std::condition_variable cv_;
121 std::shared_ptr<Media::PixelMap> pixelMap_;
122 };
123 } // namespace
124
ProcessImageNode(const RefPtr<UINode> & node)125 void ProcessImageNode(const RefPtr<UINode>& node)
126 {
127 if (node->GetTag() == V2::IMAGE_ETS_TAG) {
128 auto imageNode = AceType::DynamicCast<FrameNode>(node);
129 if (imageNode && AceType::DynamicCast<ImagePattern>(imageNode->GetPattern())) {
130 auto imagePattern = AceType::DynamicCast<ImagePattern>(imageNode->GetPattern());
131 imagePattern->SetIsComponentSnapshotNode();
132 imagePattern->OnVisibleAreaChange(true);
133 }
134 }
135 auto children = node->GetChildren();
136 for (const auto& child : children) {
137 ProcessImageNode(child);
138 }
139 }
140
CheckImageSuccessfullyLoad(const RefPtr<UINode> & node,int32_t & imageCount)141 bool CheckImageSuccessfullyLoad(const RefPtr<UINode>& node, int32_t& imageCount)
142 {
143 CHECK_NULL_RETURN(node, false);
144 auto frameNode = AceType::DynamicCast<FrameNode>(node);
145 if (frameNode && !frameNode->IsVisible()) {
146 return true;
147 }
148 if (node->GetTag() == V2::IMAGE_ETS_TAG) {
149 imageCount++;
150 auto imageNode = AceType::DynamicCast<FrameNode>(node);
151 CHECK_NULL_RETURN(imageNode, false);
152 auto imagePattern = AceType::DynamicCast<ImagePattern>(imageNode->GetPattern());
153 CHECK_NULL_RETURN(imagePattern, false);
154 auto imageLoadContext = imagePattern->GetImageLoadingContext().Upgrade();
155 CHECK_NULL_RETURN(imageLoadContext, false);
156 auto imageStateManger = imageLoadContext->GetStateManger();
157 CHECK_NULL_RETURN(imageStateManger, false);
158
159 auto result = imageStateManger->GetCurrentState() == ImageLoadingState::LOAD_SUCCESS;
160 if (!result) {
161 TAG_LOGW(AceLogTag::ACE_COMPONENT_SNAPSHOT,
162 "Image loading failed! ImageId=%{public}d ImageState=%{public}d ImageKey=%{public}s",
163 imageNode->GetId(), static_cast<int32_t>(imageStateManger->GetCurrentState()),
164 imageNode->GetInspectorId().value_or("").c_str());
165 }
166 return result;
167 }
168
169 auto children = node->GetChildren();
170 for (const auto& child : children) {
171 if (!CheckImageSuccessfullyLoad(child, imageCount)) {
172 return false;
173 }
174 }
175 return true;
176 }
177
GetTaskExecutor(const RefPtr<AceType> & customNode,RefPtr<PipelineContext> & pipeline,RefPtr<TaskExecutor> & executor)178 bool GetTaskExecutor(const RefPtr<AceType>& customNode, RefPtr<PipelineContext>& pipeline,
179 RefPtr<TaskExecutor>& executor)
180 {
181 auto uiNode = AceType::DynamicCast<UINode>(customNode);
182 if (!uiNode) {
183 TAG_LOGW(AceLogTag::ACE_COMPONENT_SNAPSHOT, "Internal error! uiNode is nullptr, "
184 "because customNode is type of %{public}s", AceType::TypeName(customNode));
185 return false;
186 }
187 pipeline = uiNode->GetContextRefPtr();
188 if (!pipeline) {
189 TAG_LOGW(AceLogTag::ACE_COMPONENT_SNAPSHOT, "Internal error! can't get pipeline");
190 return false;
191 }
192 executor = pipeline->GetTaskExecutor();
193 if (!executor) {
194 TAG_LOGW(AceLogTag::ACE_COMPONENT_SNAPSHOT, "Internal error! can't get executor");
195 return false;
196 }
197
198 return true;
199 }
200
GetRsNode(const RefPtr<FrameNode> & node)201 std::shared_ptr<Rosen::RSNode> ComponentSnapshot::GetRsNode(const RefPtr<FrameNode>& node)
202 {
203 CHECK_NULL_RETURN(node, nullptr);
204 auto context = AceType::DynamicCast<RosenRenderContext>(node->GetRenderContext());
205 CHECK_NULL_RETURN(context, nullptr);
206 auto rsNode = context->GetRSNode();
207 return rsNode;
208 }
209
Get(const std::string & componentId,JsCallback && callback,const SnapshotOptions & options)210 void ComponentSnapshot::Get(const std::string& componentId, JsCallback&& callback, const SnapshotOptions& options)
211 {
212 auto node = Inspector::GetFrameNodeByKey(componentId);
213 if (!node) {
214 callback(nullptr, ERROR_CODE_INTERNAL_ERROR, nullptr);
215 TAG_LOGW(AceLogTag::ACE_COMPONENT_SNAPSHOT,
216 "Can't find a component that id or key are %{public}s, Please check your parameters are correct",
217 componentId.c_str());
218 return;
219 }
220
221 auto rsNode = GetRsNode(node);
222
223 if (node->GetIsLayoutNode()) {
224 std::list<RefPtr<FrameNode>> children;
225 node->GetOneDepthVisibleFrame(children);
226 if (children.empty()) {
227 callback(nullptr, ERROR_CODE_INTERNAL_ERROR, nullptr);
228 TAG_LOGW(AceLogTag::ACE_COMPONENT_SNAPSHOT,
229 "Children is empty from FrameNode(InspectorId=%{public}s Id=%{public}d)",
230 componentId.c_str(), node->GetId());
231 return;
232 }
233 node = children.front();
234 rsNode = GetRsNode(children.front());
235 }
236
237 if (!rsNode) {
238 callback(nullptr, ERROR_CODE_INTERNAL_ERROR, nullptr);
239 TAG_LOGW(AceLogTag::ACE_COMPONENT_SNAPSHOT,
240 "RsNode is null from FrameNode(InspectorId=%{public}s Id=%{public}d)",
241 componentId.c_str(), node->GetId());
242 return;
243 }
244 int32_t imageCount = 0;
245 bool checkImage = CheckImageSuccessfullyLoad(node, imageCount);
246 TAG_LOGI(AceLogTag::ACE_COMPONENT_SNAPSHOT,
247 "Get ComponentSnapshot key=%{public}s options=%{public}s Id=%{public}d Tag=%{public}s imageCount=%{public}d "
248 "checkImage=%{public}d RsNodeId=%{public}" PRIu64 "",
249 componentId.c_str(), options.ToString().c_str(), node->GetId(), node->GetTag().c_str(), imageCount, checkImage,
250 rsNode->GetId());
251 auto& rsInterface = Rosen::RSInterfaces::GetInstance();
252 rsInterface.TakeSurfaceCaptureForUI(rsNode, std::make_shared<CustomizedCallback>(std::move(callback), nullptr),
253 options.scale, options.scale, options.waitUntilRenderFinished);
254 }
255
Create(const RefPtr<AceType> & customNode,JsCallback && callback,bool enableInspector,const SnapshotParam & param,bool flag)256 void ComponentSnapshot::Create(
257 const RefPtr<AceType>& customNode, JsCallback&& callback, bool enableInspector, const SnapshotParam& param,
258 bool flag)
259 {
260 if (!customNode) {
261 callback(nullptr, ERROR_CODE_INTERNAL_ERROR, nullptr);
262 TAG_LOGW(AceLogTag::ACE_COMPONENT_SNAPSHOT, "Internal error! customNode is nullptr");
263 return;
264 }
265 auto* stack = ViewStackProcessor::GetInstance();
266 auto nodeId = stack->ClaimNodeId();
267 auto stackNode = FrameNode::CreateFrameNode(V2::STACK_ETS_TAG, nodeId, AceType::MakeRefPtr<StackPattern>());
268 RefPtr<PipelineContext> pipeline = nullptr;
269 RefPtr<TaskExecutor> executor = nullptr;
270 if (!GetTaskExecutor(customNode, pipeline, executor)) {
271 callback(nullptr, ERROR_CODE_INTERNAL_ERROR, nullptr);
272 return;
273 }
274 auto node = AceType::DynamicCast<FrameNode>(customNode);
275 if (!node) {
276 RefPtr<UINode> uiNode = AceType::DynamicCast<UINode>(customNode);
277 stackNode->AddChild(uiNode);
278 node = stackNode;
279 }
280 FrameNode::ProcessOffscreenNode(node);
281 TAG_LOGI(AceLogTag::ACE_COMPONENT_SNAPSHOT,
282 "Process off screen Node finished, root size = %{public}s Id=%{public}d Tag=%{public}s InspectorId=%{public}s "
283 "enableInspector=%{public}d",
284 node->GetGeometryNode()->GetFrameSize().ToString().c_str(), node->GetId(), node->GetTag().c_str(),
285 node->GetInspectorId().value_or("").c_str(), enableInspector);
286
287 ProcessImageNode(node);
288
289 if (enableInspector) {
290 Inspector::AddOffscreenNode(node);
291 }
292
293 if (flag) {
294 executor->PostTask(
295 [node]() {
296 auto pipeline = node->GetContext();
297 CHECK_NULL_VOID(pipeline);
298 pipeline->FlushUITasks();
299 pipeline->FlushMessages();
300 },
301 TaskExecutor::TaskType::UI, "ArkUIComponentSnapshotFlushUITasks", PriorityType::VIP);
302 }
303 PostDelayedTaskOfBuiler(executor, std::move(callback), node, enableInspector, pipeline, param);
304 }
305
GetNormalCapture(const RefPtr<FrameNode> & frameNode,NormalCallback && callback)306 void ComponentSnapshot::GetNormalCapture(const RefPtr<FrameNode>& frameNode, NormalCallback&& callback)
307 {
308 auto rsNode = GetRsNode(frameNode);
309 auto& rsInterface = Rosen::RSInterfaces::GetInstance();
310 rsInterface.TakeSurfaceCaptureForUI(rsNode, std::make_shared<NormalCaptureCallback>(std::move(callback)));
311 }
312
PostDelayedTaskOfBuiler(const RefPtr<TaskExecutor> & executor,JsCallback && callback,const RefPtr<FrameNode> & node,bool enableInspector,const RefPtr<PipelineContext> & pipeline,const SnapshotParam & param)313 void ComponentSnapshot::PostDelayedTaskOfBuiler(const RefPtr<TaskExecutor>& executor, JsCallback&& callback,
314 const RefPtr<FrameNode>& node, bool enableInspector, const RefPtr<PipelineContext>& pipeline,
315 const SnapshotParam& param)
316 {
317 executor->PostDelayedTask(
318 [callback, node, enableInspector, pipeline, param]() mutable {
319 BuilerTask(std::move(callback), node, enableInspector, pipeline, param);
320 },
321 TaskExecutor::TaskType::UI, param.delay, "ArkUIComponentSnapshotCreateCapture", PriorityType::VIP);
322 }
323
BuilerTask(JsCallback && callback,const RefPtr<FrameNode> & node,bool enableInspector,const RefPtr<PipelineContext> & pipeline,const SnapshotParam & param)324 void ComponentSnapshot::BuilerTask(JsCallback&& callback, const RefPtr<FrameNode>& node, bool enableInspector,
325 const RefPtr<PipelineContext>& pipeline, const SnapshotParam& param)
326 {
327 int32_t imageCount = 0;
328 if (param.checkImageStatus && !CheckImageSuccessfullyLoad(node, imageCount)) {
329 TAG_LOGW(AceLogTag::ACE_COMPONENT_SNAPSHOT,
330 "Image loading failed! rootId=%{public}d rootNode=%{public}s InspectorId=%{public}s",
331 node->GetId(), node->GetTag().c_str(), node->GetInspectorId().value_or("").c_str());
332 Inspector::RemoveOffscreenNode(node);
333 callback(nullptr, ERROR_CODE_COMPONENT_SNAPSHOT_IMAGE_LOAD_ERROR, nullptr);
334 return;
335 }
336 if (param.options.waitUntilRenderFinished) {
337 pipeline->FlushUITasks();
338 pipeline->FlushMessages();
339 }
340 auto rsNode = GetRsNode(node);
341 auto& rsInterface = Rosen::RSInterfaces::GetInstance();
342 TAG_LOGI(AceLogTag::ACE_COMPONENT_SNAPSHOT,
343 "Begin to take surfaceCapture for ui, rootId=%{public}d param=%{public}s imageCount=%{public}d",
344 node->GetId(), param.ToString().c_str(), imageCount);
345 rsInterface.TakeSurfaceCaptureForUI(
346 rsNode,
347 std::make_shared<CustomizedCallback>(std::move(callback), enableInspector ? node : nullptr),
348 param.options.scale, param.options.scale, param.options.waitUntilRenderFinished);
349 }
350
GetSync(const std::string & componentId,const SnapshotOptions & options)351 std::pair<int32_t, std::shared_ptr<Media::PixelMap>> ComponentSnapshot::GetSync(const std::string& componentId,
352 const SnapshotOptions& options)
353 {
354 CHECK_RUN_ON(UI);
355 ACE_SCOPED_TRACE("ComponentSnapshot::GetSyncStart_%s", componentId.c_str());
356 std::pair<int32_t, std::shared_ptr<Media::PixelMap>> result(ERROR_CODE_INTERNAL_ERROR, nullptr);
357 auto node = Inspector::GetFrameNodeByKey(componentId);
358 if (!node) {
359 TAG_LOGW(AceLogTag::ACE_COMPONENT_SNAPSHOT,
360 "Can't find a component that id or key are %{public}s, Please check your parameters are correct",
361 componentId.c_str());
362 return result;
363 }
364
365 auto rsNode = GetRsNode(node);
366
367 if (node->GetIsLayoutNode()) {
368 std::list<RefPtr<FrameNode>> children;
369 node->GetOneDepthVisibleFrame(children);
370 if (children.empty()) {
371 TAG_LOGW(AceLogTag::ACE_COMPONENT_SNAPSHOT,
372 "Children is empty from FrameNode(InspectorId=%{public}s Id=%{public}d)",
373 componentId.c_str(), node->GetId());
374 return result;
375 }
376 node = children.front();
377 rsNode = GetRsNode(children.front());
378 }
379
380 if (!rsNode) {
381 TAG_LOGW(AceLogTag::ACE_COMPONENT_SNAPSHOT,
382 "RsNode is null from FrameNode(InspectorId=%{public}s Id=%{public}d)",
383 componentId.c_str(), node->GetId());
384 return result;
385 }
386 TAG_LOGI(AceLogTag::ACE_COMPONENT_SNAPSHOT,
387 "GetSync ComponentSnapshot key=%{public}s options=%{public}s Id=%{public}d Tag=%{public}s "
388 "RsNodeId=%{public}" PRIu64 "",
389 componentId.c_str(), options.ToString().c_str(), node->GetId(), node->GetTag().c_str(), rsNode->GetId());
390 auto& rsInterface = Rosen::RSInterfaces::GetInstance();
391 auto syncCallback = std::make_shared<SyncCustomizedCallback>();
392 {
393 ACE_SCOPED_TRACE("ComponentSnapshot::GetSync_TakeSurfaceCaptureForUI_%s_%d_%" PRIu64 "", componentId.c_str(),
394 node->GetId(), rsNode->GetId());
395 }
396 rsInterface.TakeSurfaceCaptureForUI(rsNode, syncCallback,
397 options.scale, options.scale, options.waitUntilRenderFinished);
398 return syncCallback->GetPixelMap(SNAPSHOT_TIMEOUT_DURATION);
399 }
400
401 // Note: do not use this method, it's only called in drag procedure process.
CreateSync(const RefPtr<AceType> & customNode,const SnapshotParam & param)402 std::shared_ptr<Media::PixelMap> ComponentSnapshot::CreateSync(
403 const RefPtr<AceType>& customNode, const SnapshotParam& param)
404 {
405 if (!customNode) {
406 TAG_LOGW(AceLogTag::ACE_COMPONENT_SNAPSHOT, "CreateSync Internal error! customNode is nullptr");
407 return nullptr;
408 }
409 auto* stack = ViewStackProcessor::GetInstance();
410 auto nodeId = stack->ClaimNodeId();
411 auto stackNode = FrameNode::CreateFrameNode(V2::STACK_ETS_TAG, nodeId, AceType::MakeRefPtr<StackPattern>());
412 RefPtr<PipelineContext> pipeline = nullptr;
413 RefPtr<TaskExecutor> executor = nullptr;
414 if (!GetTaskExecutor(customNode, pipeline, executor)) {
415 TAG_LOGW(AceLogTag::ACE_COMPONENT_SNAPSHOT, "Internal error! Can't get TaskExecutor!");
416 return nullptr;
417 }
418 auto node = AceType::DynamicCast<FrameNode>(customNode);
419 if (!node) {
420 RefPtr<UINode> uiNode = AceType::DynamicCast<UINode>(customNode);
421 stackNode->AddChild(uiNode);
422 node = stackNode;
423 }
424 FrameNode::ProcessOffscreenNode(node);
425 TAG_LOGI(AceLogTag::ACE_COMPONENT_SNAPSHOT,
426 "Process off screen Node finished, root size = %{public}s Id=%{public}d Tag=%{public}s InspectorId=%{public}s",
427 node->GetGeometryNode()->GetFrameSize().ToString().c_str(), node->GetId(), node->GetTag().c_str(),
428 node->GetInspectorId().value_or("").c_str());
429
430 ProcessImageNode(node);
431 pipeline->FlushUITasks();
432 pipeline->FlushMessages();
433 int32_t imageCount = 0;
434 bool checkImage = CheckImageSuccessfullyLoad(node, imageCount);
435 if (!checkImage) {
436 TAG_LOGW(AceLogTag::ACE_COMPONENT_SNAPSHOT,
437 "Image loading failed! rootId=%{public}d rootNode=%{public}s InspectorId=%{public}s",
438 node->GetId(), node->GetTag().c_str(), node->GetInspectorId().value_or("").c_str());
439 return nullptr;
440 }
441 auto rsNode = GetRsNode(node);
442 if (!rsNode) {
443 TAG_LOGW(AceLogTag::ACE_COMPONENT_SNAPSHOT,
444 "Can't get RsNode! rootId=%{public}d rootNode=%{public}s InspectorId=%{public}s",
445 node->GetId(), node->GetTag().c_str(), node->GetInspectorId().value_or("").c_str());
446 return nullptr;
447 }
448 auto& rsInterface = Rosen::RSInterfaces::GetInstance();
449 auto syncCallback = std::make_shared<SyncCustomizedCallback>();
450 rsInterface.TakeSurfaceCaptureForUI(rsNode, syncCallback,
451 1.f, 1.f, true);
452 return syncCallback->GetPixelMap(CREATE_SNAPSHOT_TIMEOUT_DURATION).second;
453 }
454 } // namespace OHOS::Ace::NG
455