Is there any way to achieve faster color reading than what I wrote?
class MyApi:
def __init__(self):
self.width = win32api.GetSystemMetrics(win32con.SM_CXVIRTUALSCREEN)
self.height = win32api.GetSystemMetrics(win32con.SM_CYVIRTUALSCREEN)
self.left = win32api.GetSystemMetrics(win32con.SM_XVIRTUALSCREEN)
self.top = win32api.GetSystemMetrics(win32con.SM_YVIRTUALSCREEN)
self.hwin = win32gui.GetDesktopWindow()
self.hwindc = win32gui.GetWindowDC(self.hwin)
self.srcdc = win32ui.CreateDCFromHandle(self.hwindc)
self.memdc = self.srcdc.CreateCompatibleDC()
self.bmp = win32ui.CreateBitmap()
self.bmp.CreateCompatibleBitmap(self.srcdc, self.width, self.height)
self.memdc.SelectObject(self.bmp)
def get_xy_color(self, *args):
self.memdc.BitBlt((0, 0), (self.width, self.height), self.srcdc, (self.left, self.top), win32con.SRCCOPY)
bmp = self.bmp.GetBitmapBits(False)
# colors = []
# for pixel in args:
# tmp = (pixel[1] * self.width + pixel[0]) * 4
# colors += [255 + c if c < 0 else c for c in bmp[tmp:tmp+3]][::-1]
# return colors
Uncommented get_xy_color part takes about 0.11 ms - it is far too long for my purposes. Can it be faster?
BitBlt is pretty fast, but GetBitmapBits is taking long time.
Also I can say that I don't need whole screen info, I would be happy if I would get some part of it, for example from point (100, 100) to (150, 150).
The purpose is to get a colors at least 30 times a second for Ambilight project.
Here you go jotto:
self.bmp = win32ui.CreateBitmap()
self.bmp.CreateCompatibleBitmap(self.srcdc, self.width, 80)
self.memdc.SelectObject(self.bmp)
def get_xy_color(self, left, right):
self.memdc.BitBlt((0, -500), (self.width, 580), self.srcdc, (self.left, self.top), win32con.SRCCOPY)
bmp = self.bmp.GetBitmapBits(False)
This will focus on a line between 500 and 500 px on height and full width of screen. I was just not aware that I can use negative values. I have checked how to do this by saving bitmap as a file and seeking for good solution.
For now I do not proccess whole screen and reduced time to between 10 and 20 ms. WHich is perfect result for me.
Purpose of the code was for AmbiLight projects. Colors are being send through Serial Port to Arduino, which proccess this to LED colors.
With this solution I am able to do between 39 and 60 gets&sends to Arduino - good result.
Related
I have repeatedly encountered the problem of slowing down animation when drawing various objects in Python. The problem is especially acute when the number of objects being drawn increases. I would not say that there are too many objects: > = 200 (r=10) circles are already a slide show.
I think it's not about the library I use (pyglet). Before that I used the p5py analog p5.js the brakes were even stronger (in truth, the creators of p5py know that it is much slower than its counterparts (Issues from Github)). I also tried Tkinter - I thought that a clean canvas would solve my problems. But that didn't happen. And now I run the code in Pyglet - I get a slideshow.
I also noticed that the computer starts to take off on the jet thrust of coolers when the animation is slowing down. At the same time, the CPU load of the Python process never exceeds 20% (very strange). Computer not quite a calculator: Intel i5-8300H 4 cores (8 virtual) 2.30 GHz.
What could be the problem? An animation of 1000 points should not turn into a slideshow, even if it is Python.
PS. I also think that the problem is not in my code. Exactly the same (not about the code below) code on p5.js gives smooth animation in the browser (but the browser uses a video card). But just in case, I put my last count on Pyglet - this is not the top of optimization, but it should not work so slowly:
sketch.py
import pyglet
import random
from firework import Firework
window = pyglet.window.Window(400, 400)
main_batch = pyglet.graphics.Batch()
fireworks = [Firework(main_batch)]
#window.event
def on_draw():
window.clear()
main_batch.draw()
def update(dt):
if random.random() < 0.03:
fireworks.append(Firework(main_batch))
for f in fireworks:
f.update()
if f.done():
fireworks.remove(f)
if __name__ == '__main__':
pyglet.clock.schedule_interval(update, 1 / 120.0)
pyglet.app.run()
particle.py
import p5
import pyglet
class Particle(pyglet.shapes.Circle):
def __init__(self, x, y, color, firework, batch=None):
radius = 2
if firework:
radius = 4
super().__init__(x, y, radius, radius * 5, color, batch)
self.color = color
self.firework = firework
self.opacity = 255
self._done = False
self.acc = p5.Vector(0, 0)
if self.firework:
self.vel = p5.Vector(0, p5.random_uniform(8, 12))
else:
self.vel = p5.Vector.random_2D() * p5.random_uniform(-10, -2)
#property
def batch(self):
return self._batch
def update(self):
self.acc += p5.Vector(0, -0.2)
if not self.firework:
self.vel *= 0.9
if self.opacity > 4:
self.opacity -= 4
else:
self._done = True
self.opacity = 0
self.vel += self.acc
pos = p5.Vector(self.x, self.y) + self.vel
self.x = pos.x
self.y = pos.y
self.acc *= 0
def done(self):
return self._done
firework.py
import p5
import random
from particle import Particle
class Firework:
def __init__(self, batch):
self.color = tuple(random.randint(0, 255) for _ in range(3))
self.firework = Particle(p5.random_uniform(400), 0, self.color, True, batch=batch)
self.bach = batch
self._exploded = False
self.particles = []
def done(self):
if self._exploded and len(self.particles) == 0:
return True
else:
return False
def explode(self):
for _ in range(70):
p = Particle(self.firework.x, self.firework.y, self.color, False, batch=self.firework.batch)
self.particles.append(p)
def update(self):
if not self._exploded:
self.firework.update()
if self.firework.vel.y < 0:
self._exploded = True
self.explode()
self.firework.delete()
for p in self.particles:
p.update()
if p.done():
self.particles.remove(p)
p.delete()
I am trying to implement a similar program to the following in Python using Pymunk and Pyglet. My current implementation works well at low velocities however at high speeds the block can pass through the static wall. This because in 1/60s clock cycle the block moves further than the thickness of the wall. I have seen others solve this by having a limiting speed however in my case this would not work as the velocity is important to calculate the value for PI. I would like to know if there is any way of preventing this from happening.
import pyglet
import pymunk
class Block:
"""
The class for a block
Mass: the mass the block
X: Initial x position
Y: Initial y position
PhysSpace: The physics space to add items to
RenderBatch: Batch to add block to
"""
def __init__(self, Mass, X, Y, PhysSpace, RenderBatch):
# Create body with given mass and infinite moment of inertia
self.Body = pymunk.Body(Mass, pymunk.inf)
# Set Body's position
self.Body.position = X, Y
# Create shape for body
BodyShape = pymunk.Poly.create_box(self.Body, size=(50, 50))
# Define shapes elasticity
BodyShape.elasticity = 1
# Add block to the physics space
PhysSpace.add(self.Body, BodyShape)
# Import block image
BlockImg = pyglet.image.load('res/sqr.png')
# Set anchor point of image to be the centre
BlockImg.anchor_x = BlockImg.width // 2
BlockImg.anchor_y = BlockImg.height // 2
# Create sprite for block
self.BlockSprite = pyglet.sprite.Sprite(BlockImg, x=self.Body.position.x, y=self.Body.position.y,
batch=RenderBatch)
def update(self):
# Set the position of the sprite to be equal to the position of the physics body
self.BlockSprite.position = self.Body.position
def give_velocity(self, velocity):
# Set velocity of the body
self.Body.velocity = (velocity, 0)
class Simulation(pyglet.window.Window):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Set background to be clear
pyglet.gl.glClearColor(1, 1, 1, 1)
# Set clock speed
pyglet.clock.schedule_interval(self.update, 1/60)
# Create batch to draw all the graphics with
self.Batch = pyglet.graphics.Batch()
# Create Title Label
self.TitleLabel = pyglet.text.Label(text='Block Collision Simulator', x=self.width / 2, y=self.height - 20,
batch=self.Batch, anchor_x='center', anchor_y='center', font_size=24,
color=(0, 0, 0, 255))
self.Counter = -2
self.CounterLabel = pyglet.text.Label('Counter = 0'.format(self.Counter), x=self.width / 2, y=self.height - 60, anchor_x='center',
anchor_y='center', font_size=24, color=(0, 0, 0, 255), batch=self.Batch)
# Initiate space for Physics engine
self.Space = pymunk.Space()
self.Handler = self.Space.add_default_collision_handler()
self.Handler.begin = self.coll_begin
# Create the ground in physics engine
Ground = pymunk.Poly.create_box(self.Space.static_body, size=(self.width, 20))
Ground.body.position = self.width / 2, 10
self.Space.add(Ground)
# Create the sprite for the ground
GroundImg = pyglet.image.load('res/ground.png')
self.GroundSprite = pyglet.sprite.Sprite(GroundImg, x=0, y=0, batch=self.Batch)
# Create Wall in physics engine
Wall = pymunk.Poly.create_box(self.Space.static_body, size=(20, self.height))
Wall.body.position = 10, self.height / 2
Wall.elasticity = 1
self.Space.add(Wall)
# Create the sprite for the wall
WallImg = pyglet.image.load('res/wall.png')
self.WallSprite = pyglet.sprite.Sprite(WallImg, x=0, y=0, batch=self.Batch)
self.BlockRight = Block(10000, 2 * (self.width / 3), 45, self.Space, self.Batch)
self.BlockRight.give_velocity(-100)
self.BlockLeft = Block(1, self.width / 3, 45, self.Space, self.Batch)
pyglet.app.run()
def coll_begin(self, arbiter, space, data):
self.Counter += 1
if self.Counter > 0:
self.CounterLabel.text = 'Counter: {}'.format(self.Counter)
return True
def on_draw(self):
self.clear()
self.Batch.draw()
def update(self, dt):
self.Space.step(dt)
self.BlockRight.update()
self.BlockLeft.update()
One way is as you write to limit the velocity. Another way is to call the step function with a smaller dt. (On the same note, you should almost always use a fixed value for the dt, that will help to keep the simulation stable).
One way to use smaller dt is to call the step function multiple times for each call to the update function. So you can try something like this:
def update(self, dt):
for _ in range(10):
self.Space.step(dt/10)
#self.Space.step(dt)
self.BlockRight.update()
self.BlockLeft.update()
I want to create a pinball game but the ball sometimes doesn't collide with other objects.
example: https://youtu.be/HwSXwJ4-d2w
Here's the code
import pyglet, pymunk
from pymunk.pyglet_util import DrawOptions
win = pyglet.window.Window(1280, 720, resizable=False)
options = DrawOptions()
space = pymunk.Space()
space.gravity = 0, -1000
def Ball(mass, radius, coords):
circle_moment = pymunk.moment_for_circle(mass, 0, radius)
circle_body = pymunk.Body(mass, circle_moment)
circle_shape = pymunk.Circle(circle_body, radius)
circle_shape.elasticity = 1.0
circle_body.position= coords
space.add(circle_body, circle_shape)
def BouncyCircle(mass, coords, radius):
circle_moment = pymunk.moment_for_circle(mass, 0, radius)
circle_body = pymunk.Body(mass, circle_moment, pymunk.Body.STATIC)
circle_shape = pymunk.Circle(circle_body, radius)
circle_shape.elasticity = 2.0
circle_body.position= coords
space.add(circle_body, circle_shape)
def Segment(mass, PointA, PointB, thickness):
segment_moment = pymunk.moment_for_segment(mass, PointA, PointB, thickness)
segment_body = pymunk.Body(mass, segment_moment, pymunk.Body.STATIC)
segment_shape = pymunk.Segment(segment_body, PointA, PointB, thickness)
segment_shape.elasticity = 0.7
segment_body.position = 0,0
space.add(segment_body, segment_shape)
Ball(0.1, 15, (640, 550)) #Falling Ball
BouncyCircle(1, (650,100), 40) #Ball
Segment(10, (105,55), (1195,55), 5) #Border
Segment(10, (100, 50), (100,680), 5)
Segment(10, (105, 675), (1195,675), 5)
Segment(10, (1200, 50), (1200, 680), 5)
#win.event
def on_draw():
win.clear()
space.debug_draw(options)
def update(dt):
space.step(dt)
if __name__ == "__main__":
pyglet.clock.schedule_interval(update, 1/60)
pyglet.app.run()
It also happens when it's very slow and sometimes it bounces 10 times until it stops working. Does anybody know how to fix it?
Its most likely because the ball is moving too fast. If a object, such as the ball, moves from one side of the wall to the other side of the wall in a single simulation step (a call to space.step()) the ball will tunnel through.
There are several ways to mitigate this problem. Sometimes it might be a good idea to do more than one of these.
Make sure the velocity of objects never get too high. One way to do that is to use a custom velocity function with a limit built in on the bodies that have a tendency to move too fast:
def limit_velocity(body, gravity, damping, dt):
max_velocity = 1000
pymunk.Body.update_velocity(body, gravity, damping, dt)
l = body.velocity.length
if l > max_velocity:
scale = max_velocity / l
body.velocity = body.velocity * scale
body_to_limit.velocity_func = limit_velocity
Depending on the requirements it might make more sense to clamp the velocity over multiple frames instead. Then the limit function could look like this instead:
def limit_velocity(body, gravity, damping, dt):
max_velocity = 1000
pymunk.Body.update_velocity(body, gravity, damping, dt)
if body.velocity.length > max_velocity:
body.velocity = body.velocity * 0.99
Use a smaller value for dt in the call to space.step. A simple way is to call space.step multiple times each frame in your application. This will also help to make the overall simulation more stable.
I think 1 or 2 is the best option for you.
I updated the pymunk docs with this explanation + a little more here: http://www.pymunk.org/en/latest/overview.html#object-tunneling
I have this application that is creating an alternate version of cookie clicker named pizza clicker. It's very basic but it's running really slowly and I can't get why.
import pyglet
window = pyglet.window.Window(fullscreen=True, caption="Click For Pizzas", resizable=True)
win_width = window.width
win_height = window.height
window.set_fullscreen(False)
window.set_size(win_width, win_height)
image_height = round(int(win_width/5)/1.4, 1)
class Main(object):
def __init__(self):
self.label = pyglet.text.Label('Pizzas: 0', font_size=100, color=(0, 0, 0, 255),
x=win_width//2, y=win_height - 100,
anchor_x='left', anchor_y='top')
self.points = 0
self.number = 1
def background(self):
background_img = pyglet.resource.image('pizza_clicker.png')
background_img.width = (win_width/5)*4
background_img.height = win_height
background_img.blit(int(win_width/5), 0, 0.5)
def drawSidebar(self):
width = int(win_width/5)
height = int(win_height)
sidebar_pattern = pyglet.image.SolidColorImagePattern(color=(100, 100, 100, 100))
sidebar = sidebar_pattern.create_image(width, height)
sidebar.blit(0, 0)
pizza = []
images = ('pizza_1.png', 'pizza_5.png', 'pizza_5.png', 'pizza_5.png')
for i in range (0, len(images)):
divideby = 1.4 / (i + 1)
pizza.append(pyglet.resource.image(images[i]))
pizza[i].width = width
pizza[i].height = round(width/1.4, 1)
pizza[i].blit(0, window.height - (round(width/divideby, 1)))
def getNumber(self, y):
if y > window.height - int(image_height):
self.number = 1
elif y > window.height - (int(image_height)*2):
self.number = 5
elif y > window.height - (int(image_height)*3):
self.number = 10
elif y > window.height - (int(image_height)*4):
self.number = 20
def addPoint(self):
self.points += self.number
self.label.text = 'Pizzas: %s' %self.points
#window.event
def on_mouse_press(x, y, buttons, modifiers):
if buttons & pyglet.window.mouse.LEFT and x > win_width/5:
main.addPoint()
elif buttons & pyglet.window.mouse.LEFT and x < win_width/5:
main.getNumber(y)
#window.event
def on_draw():
window.clear()
main.background()
main.label.draw()
main.drawSidebar()
main = Main()
pyglet.app.run()
So the problem is that when I click on the right side of the window, it should add a point (or many) instantly but it lags for a few seconds. Also, just so nobody get confused, the code does work, but just slowly. What should I do to solve it?
On every draw() iteration, you're doing:
background_img = pyglet.resource.image('pizza_clicker.png')
This means you're loading in the same picture, from hard-drive, every render sequence. You're also doing a for loop over different pizza images where you also fetch them from hard drive:
for i in range (0, len(images)):
divideby = 1.4 / (i + 1)
pizza.append(pyglet.resource.image(images[i]))
I strongly suggest you read up on how resources are loaded in, and use a cProfiler analyzer.
A good example of how you could profile your code, is here.
Since this is a external source, I'll include two SO's links as well that's about equally good (but not as potent or self explaining):
https://stackoverflow.com/a/23164271/929999
Using cProfile results with KCacheGrind
Here's a tl-dr version:
python -m cProfile -o profile_data.pyprof your_script.py
pyprof2calltree -i profile_data.pyprof -k
This should render a so called, "call tree", of all the executions your code did, how long they took and how much memory they used up. All the way from start to bottom of your application.
However, I strongly suggest you do 1 rendering sequence and add a exit(1) after the first render. Just so you profile 1 run, not 60 per second.
Keywords to search for to get a hang of why your code is slow: Python, profiling, kcachegrind, cprofile, cprofiling, callstack.
Spoiler alert
To solve the majority of your problems, move all I/O intensive operations (loading images, creating shapes etc) into the __init__ call of your main class.
The end product would look something like this instead:
class Main(object):
def __init__(self):
self.label = pyglet.text.Label('Pizzas: 0', font_size=100, color=(0, 0, 0, 255),
x=win_width//2, y=win_height - 100,
anchor_x='left', anchor_y='top')
self.points = 0
self.number = 1
self.background_img = pyglet.resource.image('pizza_clicker.png')
self.background_img.width = (win_width/5)*4
self.background_img.height = win_height
sidebar_pattern = pyglet.image.SolidColorImagePattern(color=(100, 100, 100, 100))
self.sidebar = sidebar_pattern.create_image(width, height)
self.pizzas = []
width = int(win_width/5)
height = int(win_height)
self.pizza_images = ('pizza_1.png', 'pizza_5.png', 'pizza_5.png', 'pizza_5.png')
for i in range (0, len(pizza_images)):
resource = pyglet.resource.image(pizza_images[i])
resource.width = width
resource.height = round(width/1.4, 1) # Not sure why you're using width here.. meh.. keeping it -yolo-
self.pizzas.append(resource)
def background(self):
self.background_img.blit(int(win_width/5), 0, 0.5)
def drawSidebar(self):
width = int(win_width/5)
height = int(win_height) # You're using win_height here, but window.height later. It's strange.
self.sidebar.blit(0, 0)
for i in range (0, len(self.pizza_images)):
divideby = 1.4 / (i + 1)
self.pizzas[i].blit(0, window.height - (round(width/divideby, 1)))
def getNumber(self, y):
if y > window.height - int(image_height):
self.number = 1
elif y > window.height - (int(image_height)*2):
self.number = 5
elif y > window.height - (int(image_height)*3):
self.number = 10
elif y > window.height - (int(image_height)*4):
self.number = 20
def addPoint(self):
self.points += self.number
self.label.text = 'Pizzas: %s' %self.points
But why stop here, there's a lot of heavy use of blit here. Blit is fine for like one or two objects. But it quickly gets hard to track what and where you're "blitting" everything to. You're also doing a whole lof of division, addition and other sorts of calculations in loops and stuff.
Remember, loops are the devil in when it comes to rendering.
If you got a loop some where, you can almost certainly start looking there for performance issues (anyone looking at this comment and going "pff he has no clue what he's saying".. Yea I know, but it's a good beginners tip).
I strongly suggest you put your images into pyglet.sprite.Sprite() objects instead. They keep track of positions, rendering and most importantly, they support batched rendering. That is your holy grail of the mother land! If anything's going to save you performance wise in pyglet.. well.. 3D rendering in general, it's batched rendering.
See, the graphic card was designed with one thing in mind.. Taking a HUGE mathematical equation and just swallow it whole. It's particularly good at taking a big fat stack of information and just shooting it to your screen. It's not as good at multiple commands. Meaning if you're sending many smaller packets back and fourth to the graphics card, it's going to perform no wear near optimum because of overhead and other things.
So, putting your images into sprites, and putting those sprites into batches, and not using any for loops and on-rendering resources loads..
This is what your code would look like:
class Main(object):
def __init__(self):
self.label = pyglet.text.Label('Pizzas: 0', font_size=100, color=(0, 0, 0, 255),
x=win_width//2, y=win_height - 100,
anchor_x='left', anchor_y='top')
self.points = 0
self.number = 1
self.background_layer = pyglet.graphics.OrderedGroup(0)
self.foreground_layer = pyglet.graphics.OrderedGroup(1)
self.batch = pyglet.graphics.Batch()
self.background_img = pyglet.sprite.Sprite(pyglet.resource.image('pizza_clicker.png'), batch=self.batch, group=self.background_layer)
self.background_img.width = (win_width/5)*4
self.background_img.height = win_height
self.background.x = int(win_width/5)
self.background.y = 0
sidebar_pattern = pyglet.image.SolidColorImagePattern(color=(100, 100, 100, 100))
self.sidebar = pyglet.sprite.Sprite(sidebar_pattern.create_image(width, height), batch=self.batch, group=self.background_layer)
self.sidebar.x = 0
self.sidebar.y = 0
self.pizzas = []
width = int(win_width/5)
height = int(win_height)
self.pizza_images = ('pizza_1.png', 'pizza_5.png', 'pizza_5.png', 'pizza_5.png')
for i in range (0, len(pizza_images)):
divideby = 1.4 / (i + 1)
resource = pyglet.sprite.Sprite(pyglet.resource.image(pizza_images[i]), batch=self.batch, group=self.foreground_layer)
resource.width = width
resource.height = round(width/1.4, 1) # Not sure why you're using width here.. meh.. keeping it -yolo-
resource.x = 0
resource.y = window.height - (round(width/divideby, 1))
self.pizzas.append(resource)
def draw(self):
# This is instead of doing:
# - self.background.draw()
# - self.sidebar.draw()
# - self.pizzas[i].draw()
self.batch.draw()
self.label.draw() # You could put this in a batch as well :)
def getNumber(self, y):
if y > window.height - int(image_height):
self.number = 1
elif y > window.height - (int(image_height)*2):
self.number = 5
elif y > window.height - (int(image_height)*3):
self.number = 10
elif y > window.height - (int(image_height)*4):
self.number = 20
def addPoint(self):
self.points += self.number
self.label.text = 'Pizzas: %s' %self.points
#window.event
def on_draw():
window.clear()
main.draw()
Now, the code ain't perfect. But it will hopefully give you a sense of the heading you should be going towards. I haven't executed this code either, mainly because I don't have all the pizza images or the time. I might come back here and do so, and tidy up the (most likely) spelling errors I have.
from Tkinter import *
import random
root = Tk()
width = 700
height = 600
canvas = Canvas(root, width = width, height = height, bg = "light blue")
canvas.pack()
pipes = []
class NewPipe:
def __init__(self, pipe_pos, pipe_hole):
self.pipe_pos = list(pipe_pos)
def update(self):
self.pipe_pos[0] -= 3
self.pipe_pos[2] -= 3
def draw(self):
canvas.create_rectangle(self.pipe_pos, fill = "green")
def get_pos(self):
return self.pipe_pos
def generate_pipe():
pipe_hole = random.randrange(0, height)
pipe_pos = [width - 100, 0, width, pipe_hole]
pipes.append(NewPipe(pipe_pos, pipe_hole))
draw_items()
canvas.after(2000, generate_pipe)
def draw_items():
for pipe in pipes:
if pipe.get_pos()[2] <= 0 - 5:
pipes.remove(pipe)
else:
pipe.draw()
pipe.update()
canvas.after(100, draw_items)
def jump(press):
pass
canvas.bind("<Button-1>", jump)
canvas.after(2000, generate_pipe)
draw_items()
mainloop()
Right now I am trying to make a game where you have to dodge rectangles, which are pipes. It is basically Flappy Bird, but on Tkinter. In this code I am trying to generate pipes and move them, but the pipes I have drawn before do not leave and they just stay there. This means that when the pipe moves, the position it was just in doesnt change and that shape stays there. Is there any way to delete past shapes, or another way to move them?
canvas.create_rectangle(self.pipe_pos, fill = "green") returns an ID.
You can use this ID to put it into methods like
canvas.coords
canvas.delete
canvas.itemconfigure
canvas.scale
canvas.type
...
Have a look at help(canvas).
The canvas is not a framebuffer on which you paint stuff for one frame. The painted stuff does not go away and you can move it and change all the parameters you can use when creating.